Search code examples
iosperformancenspredicateuisearchbardisplaycontrol

iOS - searching in a large NSArray is slow


I'm building a customised UITableViewController that shows all the contacts in the iPhone and behaves like the ABPeoplePickerNavigationController. Meaning it also supports searching the contacts. I'm doing this with the code here.

I've implemented the search ability using Search Bar and Search Display Controller, and I followed this tutorial by appcoda.

Since my NSArray is an array of ABRecordRef my method of filterContentForSearchText: scope: is this:

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
    NSPredicate *resultPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
        ABRecordRef person = (__bridge ABRecordRef)evaluatedObject;
        NSString * fullname = [self getFullnameOfRecord:person];

        NSPredicate *tmpPredicate = [NSPredicate predicateWithFormat:@"self contains[c] %@", searchText];
        if ([tmpPredicate evaluateWithObject:fullname]) {
            return YES;
        } else {
            NSLog(@"tmpPredicate didn't match");
            return NO;
        }
    }];

    searchResults = [self.allContacts filteredArrayUsingPredicate:resultPredicate];
}

The search results are fine, but since this is a very large array, it works very slowly. Is there a way I could improve the performance of this search mechanism?

Update: As @Eiko suggested, I tried replacing the inner NSPredicate with this code:

NSRange range = [fullname rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (range.length > 0) {
    return YES;
} else {
    return NO;
}

But it didn't improve the performance.


Solution

  • You should try to use the profiler to find the weakest line, but I assume that the problem is that predicate block is evaluated for every entry every time.

    I would suggest you to create your own wrapper class for ABRecordRef (let's say RecordWrapper), which will contain link to ABRecordRef with whole data and cache some frequent used and important values (e.g. fullName), you can obtain it once while loading list of contacts.

    Then if you have an array of RecordWrapper* objects you can filter simply by calling

    NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:@"fullName contains[c] %@", searchText];
    searchResults = [self.allContactsWrappers filteredArrayUsingPredicate:resultPredicate];
    

    This should significantly increase filtering speed.