我正在一个UI测试框架(KIF-next)上运行,该框架在应用程序的主线程中运行。基本过程是:

  • 执行一些测试逻辑。
  • 通过runUntilDate:将主循环旋转0.1秒。
  • 重复。

  • 这种方法效果很好。您可以模拟点击事件,可以在滑块之间滑动,观看警报视图的动画效果,scrollToRowAtIndexPath:。除了一种特定的方案(拖动滚动视图)之外,其他所有东西都运行良好。

    您可以上下拖动滚动视图,但是松开手指时什么也没有发生。此外,触摸事件将不再被其他视图识别,您所能做的就是拖动滚动视图。

    如果此执行模式结束并且代码退出,则滚动视图将进行反弹/减速,但这不是一个选项,因为我正在执行顺序执行的ocunit中工作。

    我的问题:为什么会发生这种情况,可以采取什么措施纠正它?

    我有两个演示:

    演示1:阻止您的整个应用程序

    使用此代码,您可以测试应用程序并确认一切正常,直到滚动为止。之后,没有任何效果。
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        double delayInSeconds = 2.0;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            while (YES) {
                [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
            }
        });
        return YES;
    }
    

    演示2:暂时阻止

    如果将此代码连接到按钮,则先单击按钮然后滚动,将看到与上述相同的效果。计时器完成后,您将看到您的应用程序恢复活力。
    - (IBAction)spinButtonClicked:(id)sender
    {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    }
    

    最佳答案

    正如Dave指出的,问题与运行循环模式有关。应用程序将主要驻留在kCFRunLoopDefaultMode模式下,但是当用户开始滚动时切换为UITrackingRunLoopMode,然后在滚动视图结束减速后再次返回。这似乎是为了防止正常计时器在滚动过程中触发。 (您可以在Safari中看到此内容,其中在滚动过程中动画停止更新。)

    机制似乎如下。

  • UIApplication具有堆栈,以跟踪应用程序请求的当前运行循环模式。
  • 当应用程序启动时,它会调用GSEventRunModal,后者又使用应用程序的当前运行模式CFRunLoopRunInMode来调用kCFRunLoopDefaultMode
  • 当用户开始滑动滚动视图时,特定于滚动视图的平移手势识别器进入“开始”状态并触发[[UIApplication sharedApplication] pushRunMode:UITrackingRunLoopMode requester:self]
  • 此方法将字符串压入堆栈,查看运行方式是否不同,并在由CFRunLoopStop创建的运行循环上调用GSEventRunModal
  • GSEventRunModal循环并再次调用CFRunLoopRunInMode,这次是UITrackingRunLoopMode
  • 减速完成后,手势识别器将调用[[UIApplication sharedApplication] popRunMode:UITrackingRunLoopMode requester:self]并执行类似的步骤。

  • 我长期运行的函数调用runUntilDate:的问题永远不会终止,因此CFRunLoopStop无效。一种方法是混淆pushRunMode:requester:popRunMode:requester:并用我自己的内部逻辑做类似的事情。

    由于我将不会收到任何实际的用户交互,因此我实际上采用的方法是将触摸移动事件发送到应用程序,并检查其上有哪些actionRecognizer。如果有一个UIScrollViewPanGestureRecognizer进入Began阶段,则其余的运行循环将通过UITrackingRunLoopMode完成。触摸模拟完成后,我将继续在该模式下运行,直到scrollView.dragging == NO为止。

    您可以在https://github.com/bnickel/KIF/blob/kif-next/Additions/UIView-KIFAdditions.m#L368上看到它的运行情况

    关于ios - UIScrollView和-[NSRunLoop runUntilDate:],我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/17375008/

    10-13 03:54