Search code examples
objective-ccocoagrand-central-dispatchnstimer

Obj-C, NSTimer Schedule in a loop, correct pattern?


I have a completion handler (which evaluates javascript in a web view), inside that I call a NSTimer, I’ve wrapped this up into a function…

-(void)runJSAndGoto: (WKWebView*)web
                 js: (NSString*)js
           selector: (SEL)aSelector
           position: (NSString*)position
        wait_period: (double)wait_period
          user_info: (id)user_info
{

    [web evaluateJavaScript:js completionHandler:^(id result, NSError * error) {

        [NSTimer scheduledTimerWithTimeInterval: wait_period
                                         target: self
                                       selector: aSelector
                                       userInfo: user_info
                                        repeats: NO;
        if(error)
        {
            NSLog(@"\n\n%@ error \n%@\n", position, error.description);
        }
    }];
}

So I can call different bits of javascript and on completion do another function, perhaps call some more javascript etc.

OK, here's my issue...

Later I call this function in a loop, which in turn runs a couple of other javascript calls on completion.

  • loop start
    • first js call
      • first js completes, wait 2 seconds
        • second js call

You can see where I’m going with this. Basically, the first call in the loop runs a lot (up until the first wait period) and so on. It’s a bit of a mess.

  • loop start
    • first call
    • first call
    • first call etc until 2 seconds has passed
      • second call

I want the full “stack” to complete before the next loop iteration starts. I'm writing a mac app.


Solution

  • The standard pattern you're probably looking for is this kind of thing:

    - (void) doLoop: (NSInteger) i {
        NSLog(@"%li", i);
        if (i == 0) return;
        NSTimeInterval delay = 2;
        dispatch_time_t popTime = dispatch_time(
            DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
            [self doLoop: i-1];
        });
    }
    

    If you call [self doLoop:5], this is what you see in the console (note the times, spaced about two seconds apart):

    2017-10-17 12:05:19.379507-0700 DelayLooper[1405:48726] 5
    2017-10-17 12:05:21.563981-0700 DelayLooper[1405:48726] 4
    2017-10-17 12:05:23.679977-0700 DelayLooper[1405:48726] 3
    2017-10-17 12:05:25.863872-0700 DelayLooper[1405:48726] 2
    2017-10-17 12:05:28.047187-0700 DelayLooper[1405:48726] 1
    2017-10-17 12:05:30.247125-0700 DelayLooper[1405:48726] 0
    

    You should readily be able to adapt that to whatever it is you're trying to do. For example, instead of decrementing a counting variable, you could be working your way thru a list of selectors and calling them.