如何安排块在下一个运行循环迭代中运行?

时间:2013-03-01 15:40:12

标签: objective-c cocoa cocoa-touch grand-central-dispatch nsrunloop

我希望能够在下一次运行循环迭代中执行block。它是否在下一个运行循环的开始或结束时执行并不是那么重要,只是执行被推迟到当前运行循环中的所有代码都已完成执行为止。

我知道以下内容不起作用,因为它与主运行循环交错,所以我的代码可能会在下一个运行循环中执行,但可能不会。

dispatch_async(dispatch_get_main_queue(),^{
    //my code
});

以下我认为遇到与上述相同的问题:

dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void){
    //my code
});

现在我相信以下内容可以正常运行,因为它放在当前运行循环的末尾(如果我错了,请纠正我),这实际上有用吗?

[self performSelector:@selector(myMethod) withObject:nil afterDelay:0];

间隔为0的计时器怎么样?文档说明:If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead.这是否转化为保证在下一次运行循环迭代中执行?

[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(myMethod) userInfo:nil repeats:NO];

这是我能想到的所有选项,但我仍然没有接近于在下一次运行循环迭代中执行一个块(而不是调用一个方法),并保证它不会更快。

5 个答案:

答案 0 :(得分:83)

您可能不知道运行循环在每次迭代中所做的一切。 (我之前没有研究过这个答案!)碰巧,CFRunLoopopen-source CoreFoundation package的一部分,所以我们可以看看它究竟是什么。运行循环看起来大致如下:

while (true) {
    Call kCFRunLoopBeforeTimers observer callbacks;
    Call kCFRunLoopBeforeSources observer callbacks;
    Perform blocks queued by CFRunLoopPerformBlock;
    Call the callback of each version 0 CFRunLoopSource that has been signalled;
    if (any version 0 source callbacks were called) {
        Perform blocks newly queued by CFRunLoopPerformBlock;
    }
    if (I didn't drain the main queue on the last iteration
        AND the main queue has any blocks waiting)
    {
        while (main queue has blocks) {
            perform the next block on the main queue
        }
    } else {
        Call kCFRunLoopBeforeWaiting observer callbacks;
        Wait for a CFRunLoopSource to be signalled
          OR for a timer to fire
          OR for a block to be added to the main queue;
        Call kCFRunLoopAfterWaiting observer callbacks;
        if (the event was a timer) {
            call CFRunLoopTimer callbacks for timers that should have fired by now
        } else if (event was a block arriving on the main queue) {
            while (main queue has blocks) {
                perform the next block on the main queue
            }
        } else {
            look up the version 1 CFRunLoopSource for the event
            if (I found a version 1 source) {
                call the source's callback
            }
        }
    }
    Perform blocks queued by CFRunLoopPerformBlock;
}

您可以看到有多种方法可以挂钩运行循环。您可以为所需的任何“活动”创建一个CFRunLoopObserver。您可以创建版本0 CFRunLoopSource并立即发出信号。您可以创建一对连接的CFMessagePorts,在版本1 CFRunLoopSource中包装一个,并向其发送消息。您可以创建CFRunLoopTimer。您可以使用dispatch_get_main_queueCFRunLoopPerformBlock排队阻止。

您需要根据计划块的时间以及何时需要调用块来决定使用哪些API。

例如,触摸在版本1源中处理,但如果您通过更新屏幕来处理触摸,则在核心动画事务提交之前实际不会执行该更新,这在kCFRunLoopBeforeWaiting中发生观察者。

现在假设你想在处理触摸时安排阻止,但是你希望在事务提交后执行它。

您可以为CFRunLoopObserver活动添加自己的kCFRunLoopBeforeWaiting,但此观察者可能会在Core Animation的观察者之前或之后运行,具体取决于您指定的顺序和Core Animation指定的顺序。 (核心动画目前指定的订单为2000000,但未记录,因此可能会更改。)

要确保您的块在Core Animation的观察者之后运行,即使您的观察者在 Core Animation的观察者之前运行,也不要直接在观察者的回调中调用该块。而是在此时使用dispatch_async将块添加到主队列。将块放在主队列上会强制运行循环立即从其“等待”中唤醒。它将运行任何kCFRunLoopAfterWaiting观察者,然后它将耗尽主队列,此时它将运行你的块。

答案 1 :(得分:0)

我不相信有任何API可以保证代码在下一个事件循环转换时运行。我也很好奇为什么你需要保证在循环中没有其他东西运行,尤其是主循环。

我还可以确认使用perforSelector:withObject:afterDelay确实使用基于runloop的计时器,并且在dispatch_get_main_queue()上具有与dispatch_async相似的功能。

编辑:

实际上,在重新阅读您的问题之后,听起来您只需要当前 runloop转而完成。如果这是真的,那么dispatch_async正是您所需要的。实际上,上面的所有代码都确保当前 runloop转向将完成。

答案 2 :(得分:0)

Rob的回答很棒而且很有启发性。我不是要替换它。

仅阅读UIView documentation,我发现:

  

完成

     

动画序列时要执行的块对象   结束。该块没有返回值,并且使用单个布尔值   指示动画是否实际的参数   在调用完成处理程序之前完成。如果持续时间   动画为0,则此块在   下一个运行循环周期。此参数可以为NULL。

一个简单的解决方案是:

UIView.animate(withDuration: 0) {
    // anything
}

答案 3 :(得分:-3)

mainQueue上的

dispatch_async是一个很好的建议,但它不会在下一个运行循环中运行,它会被插入到循环中的当前运行中。

要获得您所追求的行为,您将需要采用传统方式:

[self performSelector:@selector(myMethod) withObject:nil afterDelay:0];

这也带来了额外的好处,可以使用NSObject的cancelPreviousPerforms取消它。

答案 4 :(得分:-3)

我自己写了NSObject category,它接受​​基于another stackoverflow question的可变延迟值。通过传递零值,您可以有效地使代码在下一个可用的runloop迭代中运行。