46

我希望能够block在下一次运行循环迭代中执行 a 。它是在下一个运行循环的开始还是结束时执行并不那么重要,只是延迟执行,直到当前运行循环中的所有代码都执行完。

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

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];

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

4

5 回答 5

103

您可能不知道运行循环在每次迭代中所做的一切。(在我研究这个答案之前,我没有!)碰巧它CFRunLoop开源 CoreFoundation 包的一部分,所以我们可以看看它到底意味着什么。运行循环大致如下所示:

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您可以为您想要的任何“活动”创建一个被调用的对象。您可以创建版本 0CFRunLoopSource并立即发出信号。您可以创建一对连接的CFMessagePorts,将一个包装在版本 1CFRunLoopSource中,然后向其发送消息。您可以创建一个CFRunLoopTimer. 您可以使用dispatch_get_main_queue或对块进行排队CFRunLoopPerformBlock

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

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

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

CFRunLoopObserver你可以为活动添加你自己的kCFRunLoopBeforeWaiting,但是这个观察者可能会在 Core Animation 的观察者之前或之后运行,这取决于你指定的顺序和 Core Animation 指定的顺序。(Core Animation 当前指定了 2000000 的顺序,但没有记录,因此可能会更改。)

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

于 2013-03-01T23:16:49.223 回答
2

Rob 的回答很棒而且内容丰富。我不是想取代它。

只是阅读UIView 文档,我发现:

完成

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

所以一个简单的解决方案是:

UIView.animate(withDuration: 0) {
    // anything
}
于 2019-01-25T13:08:11.183 回答
0

我不相信有任何 API 可以让您保证代码在下一个事件循环轮次中运行。我也很好奇为什么你需要保证没有其他东西在循环上运行,特别是主要的。

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

编辑:

实际上,在重新阅读您的问题后,听起来您只需要完成当前的runloop 即可。如果这是真的,那么 dispatch_async 正是你所需要的。事实上,上面的所有代码都保证了当前的runloop 循环将完成。

于 2013-03-01T16:25:55.937 回答
-3

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

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

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

这也带来了额外的优势,即可以使用 NSObject 的 cancelPreviousPerforms 取消它。

于 2013-05-31T08:07:17.787 回答
-3

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

于 2013-05-31T08:32:47.250 回答