Search code examples
iphoneiosobjective-cios6grand-central-dispatch

Speed up search using dispatch_async?


I'm trying to speed up my app search , it get lags when there is a lot of data.

so i'm trying to split search Predicate on UI by using dispatch_async not dispatch_sync cause no different if I use it.

The problem is when i use dispatch_async, the app crash sometimes because [__NSArrayI objectAtIndex:]: index "17" beyond bounds.

I now this happened because lets say the first one still work and reload the tableView and continue search will change the array size depend on result so in this case "CRASH" :(

this is my code:

    dispatch_async(myQueue, ^{
        searchArray = [PublicMeathods searchInArray:searchText array:allData];
    } );

    if(currentViewStyle==listViewStyle){
        [mytable reloadData];
    }

and i've tried this :

    dispatch_async(myQueue, ^{
        NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
        dispatch_sync(dispatch_get_main_queue(), ^{
            searchArray = tmpArray;
            [mytable reloadData];
        });
    });

but in this case the lags still there.

Update -1- :

The search Predicate takes just 2ms :) after hard work :) but the keyboard still lags when the user searches, so the only thing I do after get result is reload table "change in UI" this what I think make it lags,

So what I search for split this two operation "typing on keyboard & refresh UI".

Update -2- :

@matehat https://stackoverflow.com/a/16879900/1658442

and

@TomSwift https://stackoverflow.com/a/16866049/1658442

answers work like a charm :)


Solution

  • One solution might be to voluntarily induce a delay between searches to let the user type and let the search be performed asynchronously. Here's how:

    First make sure your queue is created like this :

    dispatch_queue_t myQueue = dispatch_queue_create("com.queue.my", DISPATCH_QUEUE_CONCURRENT);
    

    Have this ivar defined in your class (and set it to FALSE upon initialization):

    BOOL _scheduledSearch;
    

    Write down this macro at the top of your file (or anywhere really, just make sure its visible)

    #define SEARCH_DELAY_IN_MS 100
    

    And instead of your second snippet, call this method:

    [self scheduleSearch];
    

    Whose implementation is:

    - (void) scheduleSearch {
        if (_scheduledSearch) return;
        _scheduledSearch = YES;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)((double)SEARCH_DELAY_IN_MS * NSEC_PER_MSEC));
        dispatch_after(popTime, myQueue, ^(void){
            _scheduledSearch = NO;
            NSString *searchText = [self textToSearchFor];
            NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
            dispatch_async(dispatch_get_main_queue(), ^{
                searchArray = tmpArray;
                [mytable reloadData];
            });
            if (![[self textToSearchFor] isEqualToString:searchText])
                [self scheduleSearch];
        });
    }
    

    [self textToSearchFor] is where you should get the actual search text from.

    Here's what it does :

    • The first time a request comes in, it sets the _scheduledSearch ivar to TRUE and tells GCD to schedule a search in 100 ms
    • Meanwhile any new search requests is not taken care of, because a search is going to happen anyway in a few ms
    • When the scheduled search happens, the _scheduledSearch ivar is reset to FALSE, so the next request is handled.

    You can play with different values for SEARCH_DELAY_IN_MS to make it suit your needs. This solution should completely decouple keyboard events with workload generated from the search.