UIScrollView和 - [NSRunLoop runUntilDate:]

Bri*_*kel 2 uikit nsrunloop ios

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

  1. 执行一些测试逻辑.
  2. 通过旋转主循环0.1秒runUntilDate:.
  3. 重复.

这种方法效果非常好.您可以模拟点击事件,您可以在滑块刷卡,观看警示的意见动画进出,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;
}
Run Code Online (Sandbox Code Playgroud)

演示2:暂时阻止

如果您连线该代码到一个按钮,你会看到同样的效果如上,如果你的第一次轻触按钮,然后滚动.计时器完成后,您将看到您的应用程序恢复生机.

- (IBAction)spinButtonClicked:(id)sender
{
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
}
Run Code Online (Sandbox Code Playgroud)

Bri*_*kel 8

正如Dave指出的那样,问题与运行循环模式有关.应用程序将主要驻留在kCFRunLoopDefaultMode模式中,但UITrackingRunLoopMode在滚动视图完成减速时用户开始滚动时再切换回来.这似乎是为了防止正常计时器在滚动期间触发.(您可以在Safari中看到此动画,其中动画在滚动期间停止更新.)

机制似乎如下.

  1. UIApplication有一个堆栈来跟踪应用程序请求的当前运行循环模式.
  2. 当应用程序启动时,它会调用GSEventRunModal哪个应用程序调用CFRunLoopRunInMode当前的运行模式kCFRunLoopDefaultMode.
  3. 当用户开始滑动滚动视图时,滚动视图特定的平移手势识别器进入开始状态并触发[[UIApplication sharedApplication] pushRunMode:UITrackingRunLoopMode requester:self].
  4. 此方法将字符串压入堆栈,看到运行模式不同,并调用CFRunLoopStop由此创建的运行循环GSEventRunModal.
  5. GSEventRunModalCFRunLoopRunInMode再次循环和调用,这一次UITrackingRunLoopMode.
  6. 当减速完成时,手势识别器调用[[UIApplication sharedApplication] popRunMode:UITrackingRunLoopMode requester:self]并发生类似的步骤.

我长时间运行的函数调用runUntilDate:它永远不会终止的CFRunLoopStop问题没有任何影响.一种方法是调和pushRunMode:requester:,popRunMode:requester:并用我自己的内部逻辑做类似的事情.

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

您可以在https://github.com/bnickel/KIF/blob/kif-next/Additions/UIView-KIFAdditions.m#L368上看到它的实际效果