0

I'm currently working on an app that downloads a substantial number of contacts through a web service and inserts the parsed data into the iOS address book. Because it's not unheard of for the users of the app to have 10-15 thousand contacts, my initial approach was to break things up into discrete operations (one operation being a download/parse/insert of 1500 contacts) and have them run individually in the background. For example, if I was to download 20000+ contacts the code would look something like the following.

for(int i = 0; i < 20047; i+=1500)
{   
    NSString* url = [NSString stringWithFormat:@"http://XXXXXX.XXXX/XXXXX"];
    NSString* urlEncodedString = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *URL = [NSURL URLWithString:urlEncodedString];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    op.responseSerializer = [AFJSONResponseSerializer serializer];
    [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
        {
            [self parseJSON:responseObject];

        });
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Error: %@", error);
    }];

    [self.downloadQueue addOperation:op];
}

For reference, the "parseJSON" function looks roughly like the following:

CFErrorRef error = NULL;
ABAddressBookRef ab = ABAddressBookCreateWithOptions(NULL, &error);

for(NSDictionary* _contactInfo in _contacts)
{
    ABRecordRef aRecord = ABPersonCreate();

    .............
    //Strip the record info and insert it into the ABRecordRef
    .............

    //Add the record to the previously created group (ID equal to 1 in this case.)
    if(ABAddressBookAddRecord(ab, aRecord, nil))
    {

        ABRecordRef ieGroup = ABAddressBookGetGroupWithRecordID(ab,1);

        ABGroupAddMember(ieGroup, aRecord, nil);
    }

    CFRelease(aRecord);

}

ABAddressBookSave(ab, nil);

CFRelease(ab);

This approach seems to work well when the number of contacts is less than 10000, but starts to break down in the 15000 range. I've been able to download 16000+ contacts, but it tends to be flaky and has a habit of crashing around the 12000 mark at the ABAddressBookSave function.

While doing some simple timing tests it seems like the biggest bottleneck is the ABAddressBookSave. Every subsequent save operation takes longer and longer (on the order of minutes, not seconds) which isn't terribly surprising since I'm guessing each operation has to wait longer on the lock for the previous one to finish.

My question is whether anyone has dealt with this type of situation and whether or not there is a better solution? Would downloading/parsing/inserting everything sequentially a better idea and less prone to problems?

prestona
  • 93
  • 1
  • 6
  • 1
    I'm trying to imagine what a good reason would be for downloading 15,000 contacts to my device. – DOK Mar 31 '14 at 19:31
  • 1
    If you *simply must* do this, probably the best approach is to download the data as a flat file and then use an offline process of some sort to insert it into whatever. But the "whatever" would more properly be your own DB rather than the Address Book. – Hot Licks Mar 31 '14 at 19:58
  • @HotLicks Downloading everything to a flat file was an option I've been weighing, but as mentioned in the post the bottleneck seems to be the addressbook save, so I'm not sure how much that would help. I still need to insert X number of contacts into the addressbook no matter which way you cut it. I would think (hope?) that the underlying mechanism for ABAddressBookSave would be the same as a Core Data insert, but I could be wrong on that account. It's looks like it's something I'll need to explore in greater detail. – prestona Mar 31 '14 at 21:48
  • It's quite possible that the save operation is not done under a single SQLite transaction, and hence each individual write is separately committed. This slows down writing by a factor of 100, easily, and is the classical cause of poor performance with multiple writes. – Hot Licks Mar 31 '14 at 21:59

0 Answers0