我最近遇到了延迟选择器未被触发的问题(NSTimer和使用performSelector:withObject:afterDelay
调用的方法)。
我已经阅读过Apple的文档,并且在特殊考虑范围内确实提到了
此方法向其当前上下文的runloop注册,并依赖于定期运行的runloop以正确执行。您可以调用此方法并最终使用不会自动定期运行的runloop注册的一个常见上下文是由调度队列调用。如果在调度队列上运行时需要此类功能,则应使用dispatch_after和相关方法来获取所需的行为。
除了当前上下文部分的runloop之外,这是完全合理的。我发现自己对于它实际上会遇到哪种情况感到困惑。它是处理所有事件的线程的主要runloop,还是在我们不知情的情况下它可能是另一个事件?
例如,如果在调用作为CoreAnimation完成块的块内的performSelector之前遇到断点,则调试器显示执行在主线程上。但是,调用performSelector:withObject:afterDelay
从未实际运行选择器。这让我觉得call有效地注册了与CoreAnimation框架相关的runloop,所以无论在主线程上执行performSelector
调用,如果CoreAnimation没有轮询其runloop,那么操作就是& #39; t执行。
用performSelectorOnMainThread:WithObject:waitUntilDone
替换该块内的此调用可以解决问题,但我很难说服同事这是根本原因。
更新:我能够将问题的根源追溯到UIScrollViewDelegate回调。有意义的是,当调用UI委托回调时,主runloop将在UITrackingRunLoopMode中。但是在那时,处理程序将在后台队列上排队一个块,然后执行将跳过其他几个队列,最终返回到主runloop。问题是当它回到主runloop时,它仍然在UITrackingRunLoopMode中。我认为当委托方法完成时,主要的runloop应该已经脱离UITracking模式,但是当执行返回到主runloop时,它仍然处于该模式。推迟从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.
}
答案 0 :(得分:1)
performSelector:withObject:afterDelay
this will call the selector on the thread that this function is called.
performSelectorOnMainThread:WithObject:waitUntilDon
,this will make sure that the selector is called on main thread
运行循环是与线程关联的基础架构的一部分。运行循环是一个事件处理循环,用于计划工作并协调传入事件的接收。运行循环的目的是在有工作时保持线程忙,并在没有线程时让线程进入休眠状态。
答案 1 :(得分:1)
每个线程只有一个运行循环,所以如果你在主线程上,那么你也在主运行循环上。但是,运行循环可以以不同的模式运行。
您可以尝试一些事情来解决问题的根源:
您可以使用+[NSRunLoop currentRunLoop]
和+[NSRunLoop mainRunLoop]
来验证您是否正在从主线程和主运行循环执行。
您还可以直接使用NSTimer的当前运行循环来安排延迟的执行选择器。 E.g:
void (^completionBlock)(BOOL) = ^(BOOL finished) {
NSCAssert([NSRunLoop currentRunLoop] == [NSRunLoop mainRunLoop], @"We're not on the main run loop");
NSRunLoop* runLoop = [NSRunLoop mainRunLoop];
// Immediate invocation.
[runLoop performSelector:@selector(someMethod) target:self argument:nil order:0 modes:@[NSDefaultRunLoopMode]];
// Delayed invocation.
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(someMethod) userInfo:nil repeats:NO];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
};
这些调用基本上等同于-performSelector:withObject:
和-performSelector:withObject:afterDelay:
。
这允许您确认您正在使用哪个运行循环。如果您在主运行循环中并且延迟调用没有运行,则主运行循环可能在默认模式下不提供服务定时器的模式下运行。例如,当UIScrollView跟踪触摸输入时,可能会发生这种情况。
答案 2 :(得分:1)
-performSelector:withObject:afterDelay:
不会在调度队列上调度操作;它在当前线程的运行循环>>上安排。每个线程都有一个运行循环,但有人必须运行运行循环才能对其执行操作。所以这一切都取决于运行此代码的线程。
如果它在主线程上运行,则将在主线程的运行循环上调度操作。在基于事件的应用程序中,UIApplicationMain
函数会调用main
,该函数在应用程序的整个生命周期内在主线程上运行一个运行循环。
如果这是在您创建的另一个线程上运行,那么该操作将被放在该线程的运行循环中。但除非你明确地运行线程的运行循环,否则运行循环上安排的操作将不会运行。
如果在GCD调度队列上运行,则表示它在某个未知线程上运行。 GCD调度队列以对用户不透明的方式在内部管理线程。通常没有人会在这样的线程上运行run循环,因此运行循环上安排的操作将不会运行。 (当然,你可以在调度操作的同一个地方显式运行run循环,但这会阻塞线程,从而阻塞调度队列,这没有多大意义。)