-1

I have a search display controller which hits an API endpoint. My current code will make a request to the API endpoint on every single char. What I want to do it make a request only when the user has stop typing for 500ms.

Here is the code:

In the UISearchDisplayDelegate

Note: searchQueue is an NSOperationQueue object.

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
    [self.searchQueue cancelAllOperations];

    [self.searchQueue addOperationWithBlock:^(){
        [self.AFRequestManager.operationQueue cancelAllOperations];

        NSString *access_token = [[FBSDKAccessToken currentAccessToken] tokenString];

        NSDictionary *params = @{@"name": searchString, @"access_token": access_token };

        NSString *getUrl = [baseUrl stringByAppendingString:@"api/users/search"];
        [self.AFRequestManager GET:getUrl parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
            self.searchedUsers = responseObject;
            [self.searchDisplayController.searchResultsTableView reloadData];

        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"Error: %@", error);
        }];
    }];

    return NO;
}

This delegate method gets called for every character that the user typed in and I would like to wait until the user finishes specifying the name.

I have tried using NSTimer but it's messy. I can definitely pass the searchString to userInfo. However, once I invalidate the NSTimer, it cannot be used again.

I have tried using a dispatch_after but actually that does not work because every time the user enter a char, the search is delayed but it is still making a request for every single character the user enters.

I did not want to overcomplicate but I feel like it should be super easy and I'm missing something.

oky_sabeni
  • 7,672
  • 15
  • 65
  • 89
  • You could use userInfo in the NSTimer to pass the search string to the selector, or use dispatch_after, create a new request when the user types a letter and cancel any delayed request. Either way, requests are stacked and need to be canceled at a point. – Thomas Mar 30 '15 at 19:10
  • I did try dispatch_after but that will still enqueue 3 requests that are delayed by 500ms. I only want 1 request unless the user does not type a character for 500ms. – oky_sabeni Mar 30 '15 at 20:17

2 Answers2

1

What about calling performSelector:withObject:afterDelay:? It's available to all NSObjects so you could call it from your view controller. The only thing you might have to worry about is not taking on every single event and queuing up 2 minutes worth of delays:

- (void)performSelector:(SEL)aSelector
             withObject:(id)anArgument
             afterDelay:(NSTimeInterval)delay

Example (note: I made up the variable names for the purpose of demonstration):

[self performSelector:@selector(callServer:) withObject:params afterDelay:0.5f];

UPDATE:

It appears as though the first part of my answer won't suffice. Here are some new steps that should help you derive a solution:

1) Create one NSTimer property so that you can access it wherever you need to

2) Move your current logic in searchDisplayController:shouldReloadTableForSearchString: into a new method so that your timer can use it as its selector. For example:

- (void)callServerWithDictionary:(NSDictionary*)params;

3) Whenever searchDisplayController:shouldReloadTableForSearchString: is called, if your timer instance is null, instantiate it and set its time interval to be 500ms and its selector to be the method you created in step 1. If the timer is not null, invalidate it and set it equal to a new timer instance with the same 500ms interval and selector as before.

4) If you are losing the search string, then create a property to hold it and update it every time searchDisplayController:shouldReloadTableForSearchString: is called. Though I believe you should be able to access it with something like searchDisplayController.searchBar.text.

Brian Sachetta
  • 3,319
  • 2
  • 34
  • 45
  • Where would this code be called in my original code sample? – oky_sabeni Mar 30 '15 at 20:16
  • You could put this right below [self.searchQueue cancelAllOperations]; and then encapsulate the addOperationWithBlock logic within a separate method that gets called by the line of code I provided in my example. The only tricky thing is making sure you pass in all the right variables / proper object. – Brian Sachetta Mar 30 '15 at 20:21
  • That would still make a request for every single char the user types in. I want to only make a request when the user stops typing for 500ms. – oky_sabeni Mar 30 '15 at 20:25
  • I hear you but NSTimer is quite a pain to use. And not just because it lacks blocks. For example, in your answer, once you invalidate an NSTimer, it cannot be reused again. This will cause issues. – oky_sabeni Mar 30 '15 at 23:48
  • Even though you can't reuse the same instance, I don't see why invalidating it and then setting it to be equal a new timer won't work. – Brian Sachetta Mar 31 '15 at 00:34
  • I see. Makes sense. Just seems like a waste to re initialize the timer every time but you are right. Thanks for the suggestion. – oky_sabeni Mar 31 '15 at 01:04
  • Sure thing. Yeah, it's not an elegant solution, but it should work. – Brian Sachetta Mar 31 '15 at 02:17
0

You can use GCD dispatch_after. Here is some example.

double delayInSeconds = 3.0;

dispatch_time_t buyTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));

dispatch_after(buyTime, dispatch_get_main_queue(), ^(void)
{
   //code that will be executed after delay
}

You could use this

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
   // block will be executed 10 times
   // number of launch passes to block
});
SergStav
  • 750
  • 5
  • 19
  • I did try that but it will still enqueue 3 requests that are delayed. What I want is to only do 1 request unless the user waits for more than 500ms. – oky_sabeni Mar 30 '15 at 20:17
  • and what about dispatch_apply? I've edit answer check please. – SergStav Mar 30 '15 at 20:20
  • Did you try dispatch_apply? – SergStav Mar 30 '15 at 21:06
  • I'm not sure how dispatch_apply will work for my case. Wouldn't that still make a request for every single char the user types in. I want to only make a request when the user stops typing for 500ms. – oky_sabeni Mar 30 '15 at 23:49