1

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.

Community
  • 1
  • 1
bobsacameno
  • 765
  • 3
  • 10
  • 25

2 Answers2

1

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.

bobsacameno
  • 765
  • 3
  • 10
  • 25
hybridcattt
  • 3,001
  • 20
  • 37
  • Can I subclass or add a category to `ABRecordRef`? Because when trying to add a new file in Xcode, I don't see in the options `ABRecordRef`. Also, what to you mean by caching it? how can I cache this? – bobsacameno Aug 18 '14 at 16:07
  • @roi.holtzman Just create class inherited from NSObject which has ABRecordRef object s a property. Also it will have fullName NSString* property. You create an object of this class once, specify ABRecordRef, extract corresponding data to fullName and other needed properties. Then you will have simple foundation objects with simple NSString properties. – hybridcattt Aug 19 '14 at 12:10
  • Thanks, your answer did improve the speed of the filtering. – bobsacameno Aug 20 '14 at 17:26
0

"very large array"? I'd guess that a contact list is rather small, typically < 1k elements.

That being said, the predicate stuff probably come at a premium, and with respect to this answer a simple enumuration might be the fastest. I suggest to test (and profile) on a real device, though.

I guess that creating predicates can be a costly operation (do they need to be compiled?), so you could reuse it, or even better, just do the little "contains check" on fullname yourself (just do a search with rangeOfString:options:), omitting the predicate alltogether.

Community
  • 1
  • 1
Eiko
  • 25,601
  • 15
  • 56
  • 71
  • indeed the array is around 1k elements. I've tried to use what you offered: `NSRange range = [fullname rangeOfString:searchText options:NSCaseInsensitiveSearch];` and then to check if `range.length > 0`. But this didn't improve performance. Maybe it has something to do with the manipulation of the `ABRecordRef` that I do to turn it into an `NSString *` ? – bobsacameno Aug 18 '14 at 13:55
  • Have you measured the baseline, i.e. accessing all address records, but doing nothing with them? You cannot be faster than that. – Eiko Aug 18 '14 at 15:07
  • yes. the slowness is when typing text in the search textField. – bobsacameno Aug 18 '14 at 15:54
  • Yes, but how fast is it if you are iterating the records but do nothing with them? On filtering, you access each one, during scrolling and displaying you don't. – Eiko Aug 18 '14 at 16:21
  • Yes, that's why I ask: How fast is touching all address book entries? It's an easy test... if iterating over all items without filtering is slow, you need to look for other ways, like caching the accessed properties. Filtering 1000 Elements should be fast, but then there may be background work going on for getting the fullname. I really suggest to start with Instruments and see what's consuming the time. – Eiko Aug 18 '14 at 16:37