Search code examples
iosobjective-crunloop

Is it possible to check that main thread is idle / to drain a main run loop?


I've just read the following post and have tried to implement the approach described there:

Writing iOS acceptance tests using Kiwi - Being Agile

All the stuff described there does work perfectly. But! there is one thing that breaks determinism when I am running my acceptance tests.

Here is the repo on Github where author of the post pushed his experiments (it can be found on the bottom of the page in the comments): https://github.com/moredip/2012-Olympics-iOS--iPad-and-iPhone--source-code/tree/kiwi-acceptance-mk1

Consider this code he uses for tapping a view:

- (void) tapViewViaSelector:(NSString *)viewSelector{
    [UIAutomationBridge tapView:[self viewViaSelector:viewSelector]];
    sleepFor(0.1); //ugh
}

...where sleepFor has the following definition behind itself:

#define sleepFor(interval) (CFRunLoopRunInMode(kCFRunLoopDefaultMode, interval, false))

It is a naive attempt ('naive' is not about the author, but about the fact that it is the first thing that comes into a head) to wait for a tiny period of time until all the animations are processed and soak all the possible events that were(or could be) scheduled to a main run loop (see also this comment).

The problem is that this naive code does not work in a deterministic way. There are a bunches of UI interactions which cause fx next button tap to be pressed before the current edited textfield's keyboard is disappeared and so on...

If I just increase the time from 0.1 to fx 1 all the problems disappear, but this leads to that every single interaction like "fill in textfield with a text..." or "tap button with title..." become to cost One second!

So I don't mean just increasing a wait time here, but rather a way to make such artificial waits guarantee that I do can proceed my test case with a next step.

I hope that it should be a more reliable way to wait enough until all the stuff caused by current action (all the transitions/animations or whatever main run loop stuff) are done.

To summarize it all to be a question:

Is there a way to exhaust/drain/soak all the stuff scheduled to a main thread and its run loop to be sure that main thread is idle and its run loop is "empty"?

This was my initial solution:

// DON'T like it
static inline void runLoopIfNeeded() {
    // https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFRunLoopRef/Reference/reference.html

    while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES) == kCFRunLoopRunHandledSource);

    // DON'T like it
    if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES) == kCFRunLoopRunHandledSource) runLoopIfNeeded();
}

Solution

  • you can try this

    while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource);
    

    this will run until no more things in the run loop. you can try to change the time interval to 0.1 if 0 is not working.