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

时间:2022-04-10 01:12:00

I want to be able to execute a block on the next run loop iteration. It's not so important whether it gets executed at the beginning or the end of the next run loop, just that execution is deferred until all code in the current run loop has finished executing.

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

I know the following doesn't work because it gets interleaved with the main run loop so my code might execute on the next run loop but it might not.

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

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

The following I believe suffers the same problem as above:

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

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

Now I believe the following would work as it is placed at the end of the current run loop (correct me if I'm wrong), would this actually work?

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

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

What about a timer with a 0 interval? The documentation states: If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead. Does this translate to guaranteeing execution on the next run loop iteration?

0间隔的计时器怎么样?文档说明:如果秒小于或等于0.0,则此方法选择非负值0.1毫秒。这是否可以保证在下一次运行循环迭代中执行?

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

That's all the options I can think of but I'm still no closer to executing a block (as opposed to calling a method) on the next run loop iteration with the guarantee that it won't be any sooner.

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

4 个解决方案

#1


73  

You might not be aware of everything that the run loop does in each iteration. (I wasn't before I researched this answer!) As it happens, CFRunLoop is part of the open-source CoreFoundation package, so we can take a look at exactly what it entails. The run loop looks roughly like this:

您可能不知道运行循环在每次迭代中执行的所有操作。 (我之前没有研究过这个答案!)碰巧,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;
}

You can see that there are a variety of ways to hook into the run loop. You can create a CFRunLoopObserver to be called for any of the “activities” you want. You can create a version 0 CFRunLoopSource and signal it immediately. You can create a connected pair of CFMessagePorts, wrap one in a version 1 CFRunLoopSource, and send it a message. You can create a CFRunLoopTimer. You can queue blocks using either dispatch_get_main_queue or CFRunLoopPerformBlock.

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

You will need to decide which of these APIs to use based on when you are scheduling the block, and when you need it to be called.

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

For example, touches are handled in a version 1 source, but if you handle the touch by updating the screen, that update isn't actually performed until the Core Animation transaction is committed, which happens in a kCFRunLoopBeforeWaiting observer.

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

Now suppose you want to schedule the block while you're handling the touch, but you want it to be executed after the transaction is committed.

现在假设您要在处理触摸时调度块,但是您希望在提交事务后执行该块。

You can add your own CFRunLoopObserver for the kCFRunLoopBeforeWaiting activity, but this observer might run before or after Core Animation's observer, depending on the order you specify and the order Core Animation specifies. (Core Animation currently specifies an order of 2000000, but that is not documented so it could change.)

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

To make sure your block runs after Core Animation's observer, even if your observer runs before Core Animation's observer, don't call the block directly in your observer's callback. Instead, use dispatch_async at that point to add the block to the main queue. Putting the block on the main queue will force the run loop to wake up from its “wait” immediately. It will run any kCFRunLoopAfterWaiting observers, and then it will drain the main queue, at which time it will run your block.

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

#2


0  

I do not believe there is any API that will allow you to guarantee code gets run on the very next event loop turn. I'm also curious why you need a guarantee that nothing else has run on the loop, the main one in particular.

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

I can also confirm that using the perforSelector:withObject:afterDelay does use a runloop-based timer, and will have functionally similar behavior to dispatch_async'ing on dispatch_get_main_queue().

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

edit:

编辑:

Actually, after re-reading your question, it sounds like you only need the current runloop turn to complete. If that is true, then dispatch_async is exactly what you need. In fact, all of the code above does make the guarantee that the current runloop turn will complete.

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

#3


-3  

I wrote myself an NSObject category which accepts a variable delay value, based on another * question. By passing a value of zero you are effectively making the code run on the next available runloop iteration.

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

#4


-3  

dispatch_async on mainQueue is a good suggestion but it does not run on the next run loop it is inserted into the current run in the loop.

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

To get the behavior you are after you will need to resort to the traditional way:

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

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

This also gives the added advantage is it can be canceled using NSObject's cancelPreviousPerforms.

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

#1


73  

You might not be aware of everything that the run loop does in each iteration. (I wasn't before I researched this answer!) As it happens, CFRunLoop is part of the open-source CoreFoundation package, so we can take a look at exactly what it entails. The run loop looks roughly like this:

您可能不知道运行循环在每次迭代中执行的所有操作。 (我之前没有研究过这个答案!)碰巧,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;
}

You can see that there are a variety of ways to hook into the run loop. You can create a CFRunLoopObserver to be called for any of the “activities” you want. You can create a version 0 CFRunLoopSource and signal it immediately. You can create a connected pair of CFMessagePorts, wrap one in a version 1 CFRunLoopSource, and send it a message. You can create a CFRunLoopTimer. You can queue blocks using either dispatch_get_main_queue or CFRunLoopPerformBlock.

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

You will need to decide which of these APIs to use based on when you are scheduling the block, and when you need it to be called.

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

For example, touches are handled in a version 1 source, but if you handle the touch by updating the screen, that update isn't actually performed until the Core Animation transaction is committed, which happens in a kCFRunLoopBeforeWaiting observer.

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

Now suppose you want to schedule the block while you're handling the touch, but you want it to be executed after the transaction is committed.

现在假设您要在处理触摸时调度块,但是您希望在提交事务后执行该块。

You can add your own CFRunLoopObserver for the kCFRunLoopBeforeWaiting activity, but this observer might run before or after Core Animation's observer, depending on the order you specify and the order Core Animation specifies. (Core Animation currently specifies an order of 2000000, but that is not documented so it could change.)

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

To make sure your block runs after Core Animation's observer, even if your observer runs before Core Animation's observer, don't call the block directly in your observer's callback. Instead, use dispatch_async at that point to add the block to the main queue. Putting the block on the main queue will force the run loop to wake up from its “wait” immediately. It will run any kCFRunLoopAfterWaiting observers, and then it will drain the main queue, at which time it will run your block.

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

#2


0  

I do not believe there is any API that will allow you to guarantee code gets run on the very next event loop turn. I'm also curious why you need a guarantee that nothing else has run on the loop, the main one in particular.

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

I can also confirm that using the perforSelector:withObject:afterDelay does use a runloop-based timer, and will have functionally similar behavior to dispatch_async'ing on dispatch_get_main_queue().

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

edit:

编辑:

Actually, after re-reading your question, it sounds like you only need the current runloop turn to complete. If that is true, then dispatch_async is exactly what you need. In fact, all of the code above does make the guarantee that the current runloop turn will complete.

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

#3


-3  

I wrote myself an NSObject category which accepts a variable delay value, based on another * question. By passing a value of zero you are effectively making the code run on the next available runloop iteration.

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

#4


-3  

dispatch_async on mainQueue is a good suggestion but it does not run on the next run loop it is inserted into the current run in the loop.

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

To get the behavior you are after you will need to resort to the traditional way:

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

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

This also gives the added advantage is it can be canceled using NSObject's cancelPreviousPerforms.

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