iOS - поиск в большом NSArray идет медленно

Я создаю настроенный UITableViewController, который показывает все контакты в iPhone и ведет себя как ABPeoplePickerNavigationController. Это означает, что он также поддерживает поиск контактов. Я делаю это с кодом здесь.

Я реализовал возможность поиска с помощью Search Bar and Search Display Controllerи я последовал этому уроку по appcoda.

Так как мой NSArray является массивом ABRecordRef мой метод filterContentForSearchText: scope: это:

- (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];
}

Результаты поиска хороши, но так как это очень большой массив, он работает очень медленно. Есть ли способ улучшить производительность этого механизма поиска?

Обновление: как предложила @Eiko, я попытался заменить внутренний NSPredicate следующим кодом:

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

Но это не улучшило производительность.

2 ответа

Решение

Вы должны попытаться использовать профилировщик, чтобы найти самую слабую строку, но я предполагаю, что проблема в том, что блок предикатов каждый раз оценивается для каждой записи.

Я бы предложил вам создать свой собственный класс-оболочку для ABRecordRef (скажем, RecordWrapper), который будет содержать ссылку на ABRecordRef со всеми данными и кэшировать некоторые часто используемые и важные значения (например, fullName), вы можете получить его один раз при загрузке списка контактов,

Затем, если у вас есть массив объектов RecordWrapper*, вы можете отфильтровать их, просто вызвав

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

Это должно значительно увеличить скорость фильтрации.

"очень большой массив"? Я предполагаю, что список контактов довольно маленький, обычно < 1k элементов.

При этом, предикатный материал, вероятно, стоит дорого, и в отношении этого ответа простое перечисление может быть самым быстрым. Я предлагаю проверить (и профиль) на реальном устройстве, хотя.

Я предполагаю, что создание предикатов может быть дорогостоящей операцией (нужно ли их компилировать?), Так что вы можете использовать ее повторно или, что еще лучше, просто выполнить небольшую проверку содержимого fullname себя (просто сделайте поиск с rangeOfString:options:), опуская предикат вообще.

Другие вопросы по тегам