是否有可能检查主线程是否空闲/排出主运行循环



我刚刚读了下面的帖子,并试图实现上面描述的方法:

使用Kiwi编写iOS验收测试-敏捷

这里描述的所有东西都能完美地工作。但是!当我运行验收测试时,有一件事打破了决定论。

这是Github上的repo,这篇文章的作者推动了他的实验(可以在页面底部的评论中找到):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
}

…其中sleepFor的定义如下:

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

等待一段很短的时间,直到所有动画都被处理完,并吸收了所有可能(或可能)被安排到主运行循环中的事件,这是一种幼稚的尝试("幼稚"不是关于作者,而是关于这是进入头脑的第一件事)。

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

如果我将时间从0.1增加到fx 1,所有问题都会消失,但这会导致每个交互,如"用文本填充文本字段"或"点击带有标题的按钮"都要花费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();
}

你可以试试这个

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

this将一直运行,直到run循环中不再有东西。如果0不能工作,可以尝试将时间间隔更改为0.1。

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

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

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;
    }
};

这是我目前的解决方案,如果没有人告诉我我错了,或者先建议一个更好的答案,我将在稍后的代码中添加一些注释和解释:

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

相关内容

  • 没有找到相关文章

最新更新