snaggs
snaggs

Reputation: 5713

AddressBook contacts search is slow over big list, how to improve?

I wrote following method where I prepare list of Dictionaries of contacts: First Name, Last name and email (ignore if doesn't exist)

 ...
 // 'filter' is search String

    NSArray *lContacts = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeople(addrBook);

    for (id lPerson in lContacts) {

        BOOL bFound = NO;

    CFStringRef lFirstName = ABRecordCopyValue((__bridge ABRecordRef)lPerson, kABPersonFirstNameProperty);
    CFStringRef lLocalizedFirstName = (lFirstName != nil)? ABAddressBookCopyLocalizedLabel(lFirstName): nil;
    CFStringRef lLastName = ABRecordCopyValue((__bridge ABRecordRef)lPerson, kABPersonLastNameProperty);
    CFStringRef lLocalizedLastName = (lLastName != nil)? ABAddressBookCopyLocalizedLabel(lLastName): nil;

    ABMultiValueRef emailMultiValue = ABRecordCopyValue((__bridge ABRecordRef)lPerson, kABPersonEmailProperty);
    NSArray *emailAddresses = (__bridge NSArray *)ABMultiValueCopyArrayOfAllValues(emailMultiValue);


    if (emailAddresses == nil){

        if(lLocalizedLastName != nil)
            CFRelease(lLocalizedLastName);
        if(lLastName != nil)
            CFRelease(lLastName);
        if(lLocalizedFirstName != nil)
            CFRelease(lLocalizedFirstName);
        if(lFirstName != nil)
            CFRelease(lFirstName);
        continue;
    }

        NSString *firstName = nil;
        NSString *lastName = nil;
        NSString *email = nil;

         NSMutableDictionary *aContact =[[NSMutableDictionary alloc] init];

        if(lLocalizedFirstName != nil) {
            firstName=[NSString stringWithFormat:@"%@",(__bridge NSString *)lLocalizedFirstName];

            if([firstName hasPrefix:filter]){
                bFound = YES;
            }
            [aContact setValue:firstName forKey:@"firstName"];
        }
        if(lLocalizedLastName != nil) {
            lastName=[NSString stringWithFormat:@"%@",(__bridge NSString *)lLocalizedLastName];
            if(bFound == NO && [lastName hasPrefix:filter]){
                bFound = YES;
            }
            [aContact setValue:lastName forKey:@"lastName"];
        }

        for (NSString* email in emailAddresses) {
            if (email != nil && [email length] > 0) {

                if(bFound == NO && [email containsString:filter]){
                    bFound = YES;
                }

                [aContact setValue:email forKey:@"email"];

                if( bFound == YES){
                    [returnContactsNew addObject:aContact];
                }

            }
        }

        if(lLocalizedLastName != nil)
            CFRelease(lLocalizedLastName);
        if(lLastName != nil)
            CFRelease(lLastName);
        if(lLocalizedFirstName != nil)
            CFRelease(lLocalizedFirstName);
        if(lFirstName != nil)
            CFRelease(lFirstName);

        if(emailAddresses != nil){
            CFRelease(emailMultiValue);
        }
 }

I fire this code when user clicks at least two letters and if User have about 300 contacts - it takes 50-60 milliseconds

I tested the same code on User device with 25K contacts, from Facebook, WhatsApp ... .. but in this case it takes 17 seconds!!! to get results.

Consider that I start search only if typing speed is faster then 300ms so first search takes 17 sec.

What wrong in my code?

I thought to use NSPredicate and first to load all contacts and after that filter them:

 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(firstName BEGINSWITH[c] $letter) OR (lastName BEGINSWITH[c] $letter) OR (email BEGINSWITH[c] $letter)"];

NSArray *filteredContacts = [returnContactsNew filteredArrayUsingPredicate:[ predicate predicateWithSubstitutionVariables:@{@"letter": filter}] ];

But from logs I get +/- the same time

Has iOS any other fast algorithm for contacts search? Because it seems a bit messy to run in loop over all 25K contacts

Upvotes: 0

Views: 840

Answers (1)

uchuugaka
uchuugaka

Reputation: 12782

The problem is you're fighting the framework a little.

The API is not designed with exposed optimizations for you to search. The provided API is designed for the user to search with the provided

ABPeoplePickerNavigationController

https://developer.apple.com/library/ios/documentation/AddressBookUI/Reference/ABPeoplePickerNavigationController_Class/index.html#//apple_ref/occ/cl/ABPeoplePickerNavigationController

That view controller does searching.

With that many records, a basic approach won't work fast enough and you should consider building a some sectioned indexes. This means you're going to have a first use slow and later use better, but need to maintain your index when the real sources change.

Given how against the framework design this really is, you might reevaluate the value of your design for what the effort and user experience will be to keep your design.

Upvotes: 1

Related Questions