哪个队列用于 - [NSObject performSelector:withObject:afterDelay]?

时间:2015-07-01 14:28:13

标签: ios objective-c nsrunloop performselector

我最近遇到了延迟选择器未被触发的问题(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.
}

3 个答案:

答案 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

What is run loop

  

运行循环是与线程关联的基础架构的一部分。运行循环是一个事件处理循环,用于计划工作并协调传入事件的接收。运行循环的目的是在有工作时保持线程忙,并在没有线程时让线程进入休眠状态。

答案 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循环,但这会阻塞线程,从而阻塞调度队列,这没有多大意义。)