Is it possible to verify that the main thread is idle / merges the main run cycle?

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

Writing iOS Acceptance Tests Using Kiwi - Agile

Everything described here works great. But! there is one thing that breaks determinism when I do my acceptance tests.

Here is a repo on Github where the author of the post pushed his experiments (you can find it at 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, which it uses to listen to the view:

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

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

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

This is a naive attempt ("naive" not about the author, but about the fact that this is the first thing that comes to mind) to wait a tiny period of time until all animations are processed and all possible events that were (or can be) planned are soaked in the main run loop (see also this comment ).

The problem is that this naive code does not work in a deterministic way. There are a bunch of user interface interactions that make you click the fx next button button before the currently edited text field keyboard disappears and so on ...

If I just increase the time from 0.1 to fx 1, all problems disappear, but this leads to the fact that each individual interaction, such as "filling the text field with text ..." or "click button with a header ...", it becomes worth one second!

So, I do not mean simply increasing the waiting time here, but rather a way to make such artificial expectations a guarantee that I can continue my test case with the next step.

I hope this will be a more reliable way to wait until all the things caused by the current action (all transitions / animations or something like the main loop of the loop) are done.

To summarize everything, you need to ask a question:

Is there a way to release / merge / absorb all the materials planned in the main thread and its cycle loop to make sure the main thread is inactive and its start cycle is “empty”?

This was my initial decision:

 // 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(); } 
+4
source share
3 answers

you can try this

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

this will work until there are more things in the run loop. you can try changing the time interval to 0.1 if 0 does not work.

+2
source

You can use CFRunLoopObserverRef to check the status of the run loop associated with the thread and register callbacks for the individual phases. This allows for extremely fine-grained control when calling callbacks. In addition, you do not need to depend on hacker timeouts, etc.

You can add this (note that I am adding one to the main loop of the loop)

 CFRunLoopObserverRef obs = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0 /* order */, handler); CFRunLoopAddObserver([NSRunLoop mainRunLoop].getCFRunLoop, obs, kCFRunLoopCommonModes); CFRelease(obs); 

Depending on the actions you register, your handler will be invoked accordingly. In the above example, the observer listens to all the actions. You will probably need kCFRunLoopBeforeWaiting

You may look like this:

 id handler = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: // About to enter the processing loop. Happens // once per `CFRunLoopRun` or `CFRunLoopRunInMode` call break; case kCFRunLoopBeforeTimers: case kCFRunLoopBeforeSources: // Happens before timers or sources are about to be handled break; case kCFRunLoopBeforeWaiting: // All timers and sources are handled and loop is about to go // to sleep. This is most likely what you are looking for :) break; case kCFRunLoopAfterWaiting: // About to process a timer or source break; case kCFRunLoopExit: // The `CFRunLoopRun` or `CFRunLoopRunInMode` call is about to // return break; } }; 
+3
source

Here is my current solution, I will add a few comments and explanations to the code a bit later if no one tells me that I am wrong, or first offers the best answer:

 // It is much better, than it was, but still unsure static inline void runLoopIfNeeded() { // https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFRunLoopRef/Reference/reference.html __block BOOL flag = NO; // http://stackoverflow.com/questions/7356820/specify-to-call-someting-when-main-thread-is-idle dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ dispatch_async(dispatch_get_main_queue(), ^{ flag = YES; }); }); while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES) == kCFRunLoopRunHandledSource); if (flag == NO) runLoopIfNeeded(); } 

Now I have no idea how this could be made more effective.

+1
source

Source: https://habr.com/ru/post/1484934/


All Articles