5

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

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

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

这是 Github 上的仓库,该帖子的作者在其中推送了他的实验(可以在页面底部的评论中找到):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
}

... wheresleepFor以下定义

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

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

问题是这个幼稚的代码不能以一种确定的方式工作。有一堆 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();
}
4

3 回答 3

5

你可以试试这个

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

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

于 2013-06-07T02:28:37.657 回答
4

要检查与线程关联的运行循环的状态并为单独的阶段注册回调,您可以使用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;
    }
};
于 2014-10-24T03:53:48.717 回答
1

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

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

现在我不知道如何使这更有效。

于 2013-06-07T02:55:08.947 回答