【发布时间】:2015-07-01 14:28:13
【问题描述】:
我最近遇到了延迟选择器未触发的问题(NSTimer 和使用performSelector:withObject:afterDelay 调用的方法)。
我已经阅读了 Apple 的文档,并且在特殊注意事项区域中确实提到了,
此方法注册到其当前上下文的 runloop,并依赖于定期运行的 runloop 才能正确执行。您可能调用此方法并最终注册到一个不会定期自动运行的 runloop 的一个常见上下文是当被调度队列调用时。如果你在 dispatch queue 上运行时需要这种类型的功能,你应该使用 dispatch_after 和相关的方法来获得你想要的行为。
这很有意义,除了当前上下文部分的运行循环。我发现自己对它实际上要去哪个运行循环感到困惑。是线程的主运行循环处理所有事件,还是在我们不知情的情况下是不同的?
例如,如果我在作为 CoreAnimation 完成块调用的块内调用 performSelector 之前遇到断点,则调试器会显示执行在主线程上。但是,调用 performSelector:withObject:afterDelay 从未真正运行选择器。这让我认为调用有效地注册了与 CoreAnimation 框架关联的 runloop,因此无论在主线程上执行 performSelector 调用,如果 CoreAnimation 不轮询它的 runloop,则不会执行操作。
用performSelectorOnMainThread:WithObject:waitUntilDone 替换该块内的这个调用可以解决问题,但我很难说服同事这是根本原因。
更新:我能够将问题的根源追溯到 UIScrollViewDelegate 回调。有意义的是,当调用 UI 委托回调时,主运行循环将处于 UITrackingRunLoopMode。但是此时,处理程序将在后台队列中排队一个块,然后执行将跳过其他几个队列,最终回到主运行循环。问题是当它回到主运行循环时,它仍然处于 UITrackingRunLoopMode。我认为当委托方法完成时主运行循环应该已经退出 UITracking 模式,但是当执行回到主运行循环时,它仍然处于该模式。从 UIScrollViewDelegate 方法推迟启动作业后台排队的代码可以解决问题,例如[self performSelector:@selector(sendTaskToBackQueue) withObject:nil afterDelay:0 inModes:@[NSDefaultRunLoopMode]]。后台任务排队回到主线程时使用的runloop模式是否可能取决于runloop在后台任务排队时所处的模式?
基本上,唯一的变化就是从这个开始......
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Currently in UITrackingRunLoopMode
dispatch_async(someGlobalQueue, someBlock);
// Block execution hops along other queues and eventually comes back to main runloop and will still be in tracking mode.
}
到这里
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Currently in UITrackingRunLoopMode
[self performSelector:@selector(backQueueTask) withObject:nil afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
}
-(void)backQueueTask {
// Currently in NSDefaultRunLoopMode
dispatch_async(someGlobalQueue, someBlock);
// Hops along other queues and eventually comes back to main runloop and will still be in NSDefaultRunLoopMode.
// It's as if the runloop mode when execution returns was dependent on what it was when the background block was queued.
}
【问题讨论】:
标签: ios objective-c nsrunloop performselector