Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Slow UISearchDisplayController with Core Data

rui.bprui.bp FrancePosts: 12New Users *
Hello,

have a TableViewController displaying like 40 000 rows from Core Data with NSFetchedResultsController.

I implemented a live search with a UISearchDisplayController (support for IOS 7). It's working but typing on the keyboard when searching is very slow...

I'd really appreciate if someone could point me to the right direction and show me where I might be going wrong.

Here is the UISearchResultsUpdating part in my TableViewController
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    ItemSearchScope scopeKey = controller.searchBar.selectedScopeButtonIndex;
    [self searchForText:searchString scope:scopeKey];
    return YES;
}

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
    NSString *searchString = controller.searchBar.text;
    [self searchForText:searchString scope:searchOption];
    return YES;
}

- (void)searchForText:(NSString *)searchText scope:(ItemSearchScope)scopeOption
{
    if (self.managedObjectContext)
{
    NSString *predicateFormat = @%K CONTAINS[cd] %@";

    NSString *searchAttribute1 = @attribute1;
    NSString *searchAttribute2 = @attribute2;
    NSString *searchAttribute3 = @attribute3;

    if (scopeOption == searchScopeDebut) {
        predicateFormat = @%K BEGINSWITH[cd] %@";
    }

    if (scopeOption == searchScopeFin) {
        predicateFormat = @%K ENDSWITH[cd] %@";
    }

    NSPredicate *p1 = [NSPredicate predicateWithFormat:predicateFormat, searchAttribute1, searchText];
    NSPredicate *p2 = [NSPredicate predicateWithFormat:predicateFormat, searchAttribute2, searchText];
    NSPredicate *p3 = [NSPredicate predicateWithFormat:predicateFormat, searchAttribute3, searchText];

    NSPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:@[p1, p2, p3]];

    [self.searchFetchRequest setPredicate:predicate];

    NSError *error = nil;
    self.filteredList = [self.managedObjectContext executeFetchRequest:self.searchFetchRequest error:&error];
    if (error)
    {
        NSLog(@searchFetchRequest failed: %@",[error localizedDescription]);
    }
}

Replies

  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,676Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    Well, that's 3 different fields being searched against 40K records. With every key stroke. That's a lot. Maybe consider delaying the search for a moment or two after the user stops typing.

    I can't find it right now, but I seem to recall reading something that suggested those predicates can be expensive to formulate. Not what they are doing... the predicates themselves. There is a way you can store them that should help minimize the damage. You can read a little about the technique here:

    https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Predicates/Articles/pCreating.html

    ...under the "Creating Predicates Using Predicate Templates" section. So what I turned this into in my code (this is several years old and I haven't needed to do it since, so I have no idea if this is still current...):

    This was a predicate to search against an "identifier" attribute. First, a property:
    - (NSPredicate *)identifierRootPredicate
    {
       if (_identifierRootPredicate == nil)
       {
          NSString *predicateFormat = [NSString stringWithFormat:@identifier == $identifier];
          _identifierRootPredicate = [NSPredicate predicateWithFormat:predicateFormat];
       }
       return _identifierRootPredicate;
    }
    

    Then where the fetch request gets formulated:
    // identifier = (value we're searching for)
    // kIDENTIFIER = (name of the attribute)
    
    NSDictionary *variables = [NSDictionary dictionaryWithObject:identifier forKey:kIDENTIFIER];
    NSPredicate *predicate = [self identifierRootPredicate];
    
    [fetchRequest setPredicate:[predicate predicateWithSubstitutionVariables:variables]];
    

    Might be worth trying. But chances are, delaying the fetch itself is probably your best bet.
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • rui.bprui.bp FrancePosts: 12New Users *
    Indeed, delaying the search input would be a good idea: Perform the reload after the user stop typing text. Can you tell me how to do that please ?

    I read about dispatch_async too, but everything I tried seems to block the UI when changing the scope.
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,676Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    I'm quite sure that Google will turn up something useful.
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • rui.bprui.bp FrancePosts: 12New Users *
    Thank you very much for your advice. I ended up using a NSTimer for delaying the shouldReloadTableForSearchString method. searchTimerPopped selector is triggered only if the user stop typing the keyboard for 2 seconds.
    @property (nonatomic, retain) NSTimer *searchTimer;
    
    - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
    {
        if (self.searchTimer) {
            [self.searchTimer invalidate];
            self.searchTimer = nil;
        }
    
        self.searchTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(searchTimerPopped:) userInfo:searchString repeats:FALSE];
        return NO;
    }
    
    - (void)searchTimerPopped:(NSTimer *)timer {
        NSString *searchString = [timer userInfo];
        ItemSearchScope scopeKey = self.searchDisplayController.searchBar.selectedScopeButtonIndex;
        [self searchForText:searchString scope:scopeKey];
        [self.searchDisplayController.searchResultsTableView reloadData];
    }
    
Sign In or Register to comment.