【问题标题】:Is it possible to check that main thread is idle / to drain a main run loop?是否可以检查主线程是否空闲/排空主运行循环?
【发布时间】:2013-06-06 23:15:57
【问题描述】:

我刚刚阅读了以下帖子并尝试实施其中描述的方法:

Writing iOS acceptance tests using Kiwi - Being Agile

那里描述的所有东西都可以完美运行。但!当我运行验收测试时,有一件事会破坏确定性。

这里是 Github 上的 repo,该帖子的作者推送了他的实验(可以在 cmets 的页面底部找到):https://github.com/moredip/2012-Olympics-iOS--iPad-and-iPhone--source-code/tree/kiwi-acceptance-mk1

考虑一下他用于点击视图的这段代码:

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

...sleepForfollowing definition behind itself:

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

等待一小段时间直到所有动画都被处理并浸泡是一种天真的尝试(“天真”不是关于作者,而是关于它是第一个进入脑海的事实)已(或可能)安排到主运行循环的所有可能事件(另请参阅this comment)。

问题是这个幼稚的代码不能以确定性的方式工作。有一堆 UI 交互会导致在当前编辑的文本字段的键盘消失之前按下 fx 下一个按钮,等等......

如果我只是将时间从 0.1 增加到 fx 1,所有问题都会消失,但这会导致每一个交互,如“用文本填充文本字段......”或“点击带有标题的按钮......”变成花费一秒钟!

所以我的意思不仅仅是在这里增加等待时间,而是一种进行这种人为等待的方法来保证我可以继续我的测试用例进行下一步。

我希望它应该是一种更可靠的方法,可以等待足够长的时间,直到当前操作引起的所有内容(所有过渡/动画或任何主运行循环内容)都完成。

总结为一个问题:

有没有办法耗尽/排空/浸泡所有调度到主线程及其运行循环的内容,以确保主线程处于空闲状态且其运行循环为“空”?

这是我最初的解决方案:

// 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();
}

【问题讨论】:

    标签: ios objective-c runloop


    【解决方案1】:

    你可以试试这个

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

    这将一直运行,直到运行循环中没有更多的东西。如果 0 不起作用,您可以尝试将时间间隔更改为 0.1。

    【讨论】:

    • 感谢您的回答。这是我最初的解决方案,在我创建这篇文章之前。我会在几分钟内发布我当前的。我真的很高兴我们正朝着一个方向思考!
    • 我在第一次尝试时更新了我的问题,因此您可以了解我最初的想法以及它是如何演变的(希望如此)。 /cc @xlc
    【解决方案2】:

    要检查与线程关联的运行循环的状态并为单独的阶段注册回调,您可以使用CFRunLoopObserverRef。这允许对何时调用回调进行极细粒度的控制。此外,您不必依赖 hacky 超时等。

    可以像这样添加一个(注意我在主运行循环中添加一个)

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

    根据您注册的活动,您的处理程序将被适当地调用。在上面的示例中,观察者侦听所有活动。你可能只需要kCFRunLoopBeforeWaiting

    你的处理程序可能看起来像这样

    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】:

      这是我目前的解决方案,稍后我会在代码中添加一些 cmets 和解释,如果没有人告诉我我错了或先提出更好的答案:

      // 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();
      }
      

      目前我不知道如何使这更有效。

      【讨论】:

      • 我看不到 dispatch_async 的好处,您应该尝试将其设置为 while 循环而不是使用递归
      • dispatch_async 的好处是确保调度到主队列之前 runLoopIfNeeded() 被调用的任何块首先被耗尽。我的推理错了吗?
      • CFRunLoopRunInMode 周围的 while 循环应该确保这一点。唯一的问题是有些甚至可能有延迟,而您不知道延迟时间。例如动画完成处理程序将在动画持续时间之后调用,因此您必须等待动画持续时间。
      • 是的,我知道这些案例。我为此使用了特殊的帮助器 eventually() ,例如 [[theValue(eventually(^{ return hasLabelWithText(@"Some text"); })) should] beYes];这个 eventually() 帮助器基于 runLoopIfNeeded()。
      猜你喜欢
      • 2011-11-27
      • 1970-01-01
      • 2011-02-16
      • 2011-10-19
      • 2011-04-02
      • 1970-01-01
      • 2018-06-27
      • 2011-03-01
      相关资源
      最近更新 更多