Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you schedule a block to run on the next run loop iteration?

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?

[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.

like image 434
lmirosevic Avatar asked Mar 01 '13 15:03

lmirosevic


People also ask

How does a while loop iterate?

The following while loop iterates as long as n is less than 3 : With each iteration, the loop increments n and adds that value to x. Therefore, x and n take on the following values: After completing the third pass, the condition n < 3 is no longer true, so the loop terminates.

How do you restart a while loop without a label?

The continue statement can be used to restart a while, do-while, for, or label statement. When you use continue without a label, it terminates the current iteration of the innermost enclosing while, do-while, or for statement and continues execution of the loop with the next iteration.

What does the CONTINUE statement do in a while loop?

The continue statement can be used to restart a while, do-while, for, or label statement. When you use continue without a label, it terminates the current iteration of the innermost enclosing while, do-while, or for statement and continues execution of the loop with the next iteration.

What happens when a for loop executes?

When a for loop executes, the following occurs: The initializing expression initialExpression, if any, is executed. This expression usually initializes one or more loop counters, but the syntax allows an expression of any degree of complexity. This expression can also declare variables. The conditionExpression expression is evaluated.


2 Answers

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:

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.

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.

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.

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.)

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.

like image 185
rob mayoff Avatar answered Nov 07 '22 04:11

rob mayoff


Rob answer is great and informative. I'm not trying to replace it.

Just reading the UIView documentation, I found :

completion

A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL.

So an easy solution would be:

UIView.animate(withDuration: 0) {     // anything } 
like image 21
GaétanZ Avatar answered Nov 07 '22 04:11

GaétanZ