Get Deleted contact using ABAddressBookRegisterExternalChangeCallback or any other notification

558 views Asked by At

Many SO questions were raised regarding listening to iOS address book change callback. Famous question Address book sync.

But my question is narrow, i.e. How can we get which contacts were deleted during addressbook sync callback.

void MyAddressBookExternalChangeCallback (ABAddressBookRef ntificationaddressbook,CFDictionaryRef info,void *context)
{
 NSLog(@"Changed Detected......");
 /*
   NSDate *lastSyncTime = [self gettingLastSyncTime];
   // By above time, I could get which contacts are modified(kABPersonModificationDateProperty)
   // and which contacts are created newly( ABRecordGetRecordID()
   // But how can I get this contact was DELETED?
 */
}

But somebody cleared this problem in Detect what was changed..... In this, they did (a) Storing all record ids in first time (b) During sync, check all stored record ids with current address book ids, to check if they are available or not. If not, then assume it to be deleted contact (costly operation).

My question: Is there any other way to detect DELETED contact?

1

There are 1 answers

1
Ivan On

As far as I know, the only way to do it is to store contact ids in some way and check modified, cause MyAddressBookExternalChangeCallback is only called when your app is active or in background, so when your app is terminated you would not be able to track those changes. Here is my implementation of address book sync controller which just keeps up to date your local contacts with devices:

// Header.h
@interface AddressBookSyncController : NSObject

+ (instancetype)sharedInstance;
+ (NSString *)archiveFilePath;

- (void)syncAddressBook;
- (void)beginObserving;
- (void)invalidateObserving;

@end

// Implementation.m
NSString *const AddressBookSyncAllKey = @"AddressBookSyncAllKey";

@interface AddressBookSyncController ()

@property (nonatomic) ABAddressBookRef addressBook;
@property (nonatomic) BOOL isObserving;

@end

@implementation AddressBookSyncController

+ (instancetype)sharedInstance
{
    static AddressBookSyncController *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [AddressBookSyncController new];
    });

    return instance;
}

+ (NSString *)archiveFilePath
{
    // Your archive file path...
}

- (void)syncAddressBook
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (ABAddressBookGetAuthorizationStatus() != kABAuthorizationStatusAuthorized) return;

        NSString *archiveFilePath = [AddressBookSyncController archiveFilePath];
        BOOL needSyncAllContacts = [[[NSUserDefaults standardUserDefaults] objectForKey:AddressBookSyncAllKey] boolValue];
        BOOL archiveExists = [[NSFileManager defaultManager] fileExistsAtPath:archiveFilePath];

        if (!needSyncAllContacts && !archiveExists) return;

        ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
        CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
        NSInteger nPeople = ABAddressBookGetPersonCount(addressBook);
        NSMutableArray *syncContacts = [NSMutableArray arrayWithCapacity:nPeople];
        NSMutableArray *archivedContacts = archiveExists ? [[NSKeyedUnarchiver unarchiveObjectWithFile:archiveFilePath] mutableCopy] : [NSMutableArray array];

        if (needSyncAllContacts)
        {
            NSMutableArray *newContacts = [NSMutableArray array];

            for (NSInteger i = 0; i < nPeople; ++i)
            {
                ABRecordRef record = CFArrayGetValueAtIndex(allPeople, i);
                NSInteger recordId = ABRecordGetRecordID(record);
                NSDate *modificationDate = (__bridge_transfer NSDate *)ABRecordCopyValue(record, kABPersonModificationDateProperty);
                AddressContact *contact = [[archivedContacts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"recordId == %i", recordId]] firstObject];

                if (contact && [contact.modificationDate isEqualToDate:modificationDate]) continue;

                AddressContact *newContact = [AddressContact addressContactWithRecord:record];

                [syncContacts addObject:newContact];

                if (contact)
                {
                    NSUInteger idx = [archivedContacts indexOfObject:contact];
                    if (idx != NSNotFound)
                    {
                        [archivedContacts replaceObjectAtIndex:idx withObject:newContact];
                    }
                    else
                    {
                        NSLog(nil, @"idx == NSNotFound for syncAddressBook");
                    }
                }
                else
                {
                    [newContacts addObject:newContact];
                }
            }

            [archivedContacts addObjectsFromArray:newContacts];
        }
        else
        {
            for (NSInteger i = 0, l = archivedContacts.count; i < l; ++i)
            {
                AddressContact *contact = [archivedContacts objectAtIndex:i];
                ABRecordRef record = ABAddressBookGetPersonWithRecordID(addressBook, (int)contact.recordId);

                if (!record) continue;

                NSDate *modificationDate = (__bridge_transfer NSDate *)ABRecordCopyValue(record, kABPersonModificationDateProperty);

                if ([modificationDate isEqualToDate:contact.modificationDate]) continue;

                AddressContact *newContact = [AddressContact addressContactWithRecord:record];

                [syncContacts addObject:newContact];
                [archivedContacts replaceObjectAtIndex:i withObject:newContact];
            }
        }

        CFRelease(allPeople);
        CFRelease(addressBook);

        BOOL result = NO;

        if ([syncContacts count] != 0)
        {
            // Do your logic with contacts
           result = [NSKeyedArchiver archiveRootObject:archivedContacts toFile:archiveFilePath];
        }

        NSLog(@"Archiving %@", result ? @"Succeed" : @"Failed");

        [[NSUserDefaults standardUserDefaults] setObject:@(NO) forKey:AddressBookSyncAllKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
    });
}

- (void)beginObserving
{
    if (self.isObserving || ABAddressBookGetAuthorizationStatus() != kABAuthorizationStatusAuthorized) return;

    self.addressBook = ABAddressBookCreateWithOptions(NULL, nil);
    ABAddressBookRegisterExternalChangeCallback(self.addressBook, addressBookChanged, (__bridge void *)self);

    self.isObserving = YES;
}

- (void)invalidateObserving
{
    if (!self.isObserving) return;

    ABAddressBookUnregisterExternalChangeCallback(self.addressBook, addressBookChanged, (__bridge void *)self);
    CFRelease(self.addressBook);

    self.isObserving = NO;
}

void addressBookChanged(ABAddressBookRef reference, CFDictionaryRef dictionary, void *context)
{
    NSLog(@"%@ changed %@", reference, dictionary);

    ABAddressBookRevert(reference);

    [(__bridge AddressBookSyncController *)context syncAddressBook];
}

@end