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

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.

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.

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, animations: {}) { _ in
// anything
}

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

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

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

Related

How to make a method to wait till another method complete it work in Objective C?

Consider this scenario.
-(void) mainMethod
{
[self subMethod1];
[self subMethod2];
}
My assumption:
When calling the mainMethod, it will call the subMethod1 first, then the subMethod2. But, there is no rule that says "After completing the subMethod1 only, the subMethod2 should get called". If I use some delay lines or some animation with duration in subMethod1, the control won't wait for it completion. It will just start the next process. So, the subMethod2 can be called before the subMethod1 completes its work. Am I right?
Question:
How can I make subMethod2 to wait, till subMethod1 completes its work?
Note: I don't want to place subMethod2 into subMethod1
You should use a completion block (like UIView animations have blocks that are executed after the animations happen).
If you're not familiar with blocks, take a look at Apple's Short Practical Guide to Blocks.

Display UI, from a background thread without breaking the flow

I have a code executing in the background thread, which is performing some kind of computation and is within a do-while loop. Due to some changes in the requirements, I have to display a UI to prompt user for input. This UI code will have to be done in the main thread, and after the prompt is entered, the logic needs to continue. Using a dispatch_async on main thread, I can display the UI, but Step -2 should not continue, until the UI is done. What is the best way to accomplish this, without breaking the flow of the code and moving units into blocks?
For example:
-(void) compute
{
do
{
//calculate some data
// Step -1...
...
// Step -2
...
...
} while(flag)
}
Between Step 1 and Step 2, I want to display a prompt. What is the best way to do so? Is it okay, to block this background thread using a mutex, which will get fired, by the main thread after the UI is done?
For this I would use GCD (Grand Central Dispatch). You can easily execute a block synchronously on the main thread (or asynchronously if you prefer), using dispatch_sync (or dispatch_async). I personally use my wrapper class EX2Dispatch in my EX2Kit library (https://github.com/einsteinx2/EX2Kit), but it's the same thing.
As an example, you would do something like this:
dispatch_sync(dispatch_get_main_queue(), ^{
// Do some stuff to the UI
};
EDIT:
I was reading it as needing to display information to the user based on the earlier calculation, but if you need a response from the user before continuing, then the loop needs to break after showing the alert, then be called again.
You could use an instance variable to track how far into the loop you are, so that it can be resumed at the same point in the UIAlertView's button clicked delegate method.
Try this
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^(void) {
//do stuff here
});

Better way to Trigger Asynchronous Callbacks in Objective-C

I am looking for a better way to do this, if possible.
I have an asynchronous callback that updates a local sqlite database. I set a flag in a singleton variable (archiveUpdateComplete) when the update completes. I sleep in a do-while until the flag gets set to true, then I hydrate my tableview. Would like to remove sleep()! Thanks for any suggestions.
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
- (void)viewDidLoad
{
dispatch_async(kBgQueue, ^{
//Hydrate word archive table view
do {
sleep(1.0);
} while ([sharedManager archiveUpdateComplete]==NO);
[self performSelectorOnMainThread:#selector(hydrateWordArchive) withObject:nil waitUntilDone:YES];
//Run custom activity indicator
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
}
If you need to poll
Polling/sleeping is rarely necessary or good. As an alternative:
You can attach an NSTimer to the main thread's run loop.
The selector the timer calls can test [sharedManager archiveUpdateComplete]
if YES is returned, then
invalidate the timer
call [MBProgressHUD hideHUDForView:self.view animated:YES];
If you don't need to poll
There are a few immediate alternatives. Which you choose depends on what knows about what:
If your manager knows who to message following completion, then the manager can simply message it. If that must occur on the main thread you can use -[NSObject performSelectorOnMainThread:withObject:waitUntilDone:] to forward to the main thread. You may also see this approach with delegates. In the case of a singleton, it doesn't make a lot of sense to take this route.
If your manager does not know who is interested in the change/completion, your manager can post a NSNotification after the task has finished (on the current thread or from the main thread).
Key Value Observing (KVO) is another option.
Perhaps I'm missing something, but why don't you just use a completion callback for this?
In other words, you change your computation to "think" in terms of nested blocks. The first async block (on some concurrent queue) does the work of updating the database, and when it's done it dispatches another async block (to the same concurrent queue) which hydrates the tableview. Finally, from that block you dispatch_async yet another block on the main queue which updates the UI, since that's the only bit that needs to execute on the main queue.
Rather than poll, in other words, you want to chain your async operations. See COMPLETION CALLBACKS section of the man page for dispatch_async().

Interrupting a loop using NSNotification

I have a class containing a method with a loop. I need to be able to break the loop if a certain event (e.g. button press) occurs.
I am using the NSNotificationCenter to notify the class containing the loop when the button is pressed.
However, If I press the button while the loop is being executed, the notification occurs after the loop is complete instead of interrupting the loop.
I'm guessing this is because it is operating in the same thread.
So how do I get the NSNotificationCenter operating in a background / different thread? Is this possible? Or is there a better way to do it?
It's not just the notification center.
I have a class containing a method with a loop. I need to be able to break the loop if a certain event (e.g. button press) occurs.
The events for that button press come in on the main thread. If your loop is running on the main thread, then the button press itself does not get processed until your loop is finished. The notification is posted immediately, relative to the button press actually getting processed by your application.
Or, in list form:
The user presses the button.
Your loop runs out of things to do and returns.
The button press arrives in your application and is turned by the button into an action message.
You post the notification.
You receive the notification.
The delay that you're seeing is between steps 1 and 2; step 4 happens immediately after step 3.
Notifications on a local (not distributed) NSNotificationCenter are dispatched on the thread you post them from, so posting it from your action method means that it will be dispatched on the main thread. This is normal and OK.
Move the loop, not the notification, to a background thread, dispatch queue, or operation queue. If you use an operation queue, you may not need the notification at all, as you can tell an operation queue to cancel all pending operations. (Your operations will need to check at any appropriate time(s) whether they have been canceled; for reasons previously discussed, killing a thread/operation at a random time is a Bad Idea.)
Background threads, blocks, and operations can communicate back to the main thread when needed (e.g., to update the UI). To send a message through the main thread's run loop, use performSelectorOnMainThread:withObject:waitUntilDone:. To dispatch a block on the main thread, use dispatch_async and dispatch_get_main_queue. To schedule an operation on the main thread, add it to [NSOperationQueue mainQueue].
For more info, read the Concurrency Programming Guide and the Notification Programming Topics.
I would run your loop in a separate thread, and have an instance variable BOOL abort;, when your button press notification comes in, set abort = TRUE; then in the loop check this value and exit if it is true.
I would run the loop in a separate thread. Even better, make it an NSOperation so that you can call [.. cancel]. Just make sure to use performSelectorOnMainThread when updating the UI from the NSOperation object. It's not a good idea to have a long running loop on the main thread.
You can't put the notification center on another thread. That object is out of your control. The problem isn't so much that they are on the same thread as that you are not allowing the run loop, which is responsible for handling the button press, to do anything. (There's one and only one run loop per thread.) As has been stated by both edsko and Peter Hosey, the button press itself, and in fact your entire UI, is stopped while your loop is running. It is generally a good idea to put long-running operations onto a background thread, then call back to the main thread to update the UI, performSelectorOnMainThread:withObject:waitUntilDone: being an easy way to do such a call back.
That said, if you were to keep the loop on the main thread, you need to let control return to the run loop periodically so that the button press will be registered. There are two ways I can think of to do this. First, you can explicitly give the run loop control briefly during each iteration of your loop:
while( !buttonWasPressed ){
// Do work...
// Let the run loop do some processing before the next iteration.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
Or, you can make a single method that consists only of the code from your loop, and use performSelector:withObject:afterDelay: to have the method repeatedly called while still allowing the run loop to work:
- (void) loopTheLoop {
if( buttonWasPressed ) return;
// Do work...
// Run this method again as soon as possible.
[self performSelector:#selector(loopTheLoop)
withObject:nil
afterDelay:0.0];
}

Best way to make NSRunLoop wait for a flag to be set?

In the Apple documentation for NSRunLoop there is sample code demonstrating suspending execution while waiting for a flag to be set by something else.
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
I have been using this and it works but in investigating a performance issue I tracked it down to this piece of code. I use almost exactly the same piece of code (just the name of the flag is different :) and if I put a NSLog on the line after the flag is being set (in another method) and then a line after the while() there is a seemingly random wait between the two log statements of several seconds.
The delay does not seem to be different on slower or faster machines but does vary from run to run being at least a couple of seconds and up to 10 seconds.
I have worked around this issue with the following code but it does not seem right that the original code doesn't work.
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (webViewIsLoading && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
using this code, the log statements when setting the flag and after the while loop are now consistently less than 0.1 seconds apart.
Anyone any ideas why the original code exhibits this behaviour?
Runloops can be a bit of a magic box where stuff just happens.
Basically you're telling the runloop to go process some events and then return. OR return if it doesn't process any events before the timeout is hit.
With 0.1 second timeout, you're htting the timeout more often than not. The runloop fires, doesn't process any events and returns in 0.1 of second. Occasionally it'll get a chance to process an event.
With your distantFuture timeout, the runloop will wait foreever until it processes an event. So when it returns to you, it has just processed an event of some kind.
A short timeout value will consume considerably more CPU than the infinite timeout but there are good reasons for using a short timeout, for example if you want to terminate the process/thread the runloop is running in. You'll probably want the runloop to notice that a flag has changed and that it needs to bail out ASAP.
You might want to play around with runloop observers so you can see exactly what the runloop is doing.
See this Apple doc for more information.
Okay, I explained you the problem, here's a possible solution:
#implementation MyWindowController
volatile BOOL pageStillLoading;
- (void) runInBackground:(id)arg
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Simmulate web page loading
sleep(5);
// This will not wake up the runloop on main thread!
pageStillLoading = NO;
// Wake up the main thread from the runloop
[self performSelectorOnMainThread:#selector(wakeUpMainThreadRunloop:) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void) wakeUpMainThreadRunloop:(id)arg
{
// This method is executed on main thread!
// It doesn't need to do anything actually, just having it run will
// make sure the main thread stops running the runloop
}
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:#selector(runInBackground:) toTarget:self withObject:nil];
[progress setHidden:NO];
while (pageStillLoading) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[progress setHidden:YES];
}
#end
start displays a progress indicator and captures the main thread in an internal runloop. It will stay there till the other thread announces that it is done. To wake up the main thread, it will make it process a function with no purpose other than waking the main thread up.
This is just one way how you can do it. A notification being posted and processed on main thread might be preferable (also other threads could register for it), but the solution above is the simplest I can think of. BTW it is not really thread-safe. To really be thread-safe, every access to the boolean needs to be locked by a NSLock object from either thread (using such a lock also makes "volatile" obsolete, as variables protected by a lock are implicit volatile according to POSIX standard; the C standard however doesn't know about locks, so here only volatile can guarantee this code to work; GCC doesn't need volatile to be set for a variable protected by locks).
In general, if you are processing events yourself in a loop, you're Doing It Wrong. It can cause a ton of messy problems, in my experience.
If you want to run modally -- for example, showing a progress panel -- run modally! Go ahead and use the NSApplication methods, run modally for the progress sheet, then stop the modal when the load is done. See the Apple documentation, for example http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html .
If you just want a view to be up for the duration of your load, but you don't want it to be modal (eg, you want other views to be able to respond to events), then you should do something much simpler. For instance, you could do this:
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:#selector(runInBackground:) toTarget:self withObject:nil];
[progress setHidden:NO];
}
- (void)wakeUpMainThreadRunloop:(id)arg
{
[progress setHidden:YES];
}
And you're done. No need to keep control of the run loop!
-Wil
If you want to be able to set your flag variable and have the run loop immediately notice, just use -[NSRunLoop performSelector:target:argument:order:modes: to ask the run loop to invoke the method that sets the flag to false. This will cause your run loop to spin immediately, the method to be invoked, and then the flag will be checked.
At your code the current thread will check for the variable to have changed every 0.1 seconds. In the Apple code example, changing the variable will not have any effect. The runloop will run till it processes some event. If the value of webViewIsLoading has changed, no event is generated automatically, thus it will stay in the loop, why would it break out of it? It will stay there, till it gets some other event to process, then it will break out of it. This may happen in 1, 3, 5, 10 or even 20 seconds. And until that happens, it will not break out of the runloop and thus it won't notice that this variable has changed. IOW the Apple code you quoted is indeterministic. This example will only work if the value change of webViewIsLoading also creates an event that causes the runloop to wake up and this seems not to be the case (or at least not always).
I think you should re-think the problem. Since your variable is named webViewIsLoading, do you wait for a webpage to be loaded? Are you using Webkit for that? I doubt you need such a variable at all, nor any of the code you have posted. Instead you should code your app asynchronously. You should start the "web page load process" and then go back to the main loop and as soon as the page finished loading, you should asynchronously post a notification that is processed within the main thread and runs the code that should run as soon as loading has finished.
I’ve had similar issues while trying to manage NSRunLoops. The discussion for runMode:beforeDate: on the class references page says:
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. Mac OS X may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.
My best guess is that an input source is attached to your NSRunLoop, perhaps by OS X itself, and that runMode:beforeDate: is blocking until that input source either has some input processed, or is removed. In your case it was taking "couple of seconds and up to 10 seconds" for this to happen, at which point runMode:beforeDate: would return with a boolean, the while() would run again, it would detect that shouldKeepRunning has been set to NO, and the loop would terminate.
With your refinement the runMode:beforeDate: will return within 0.1 seconds, regardless of whether or not it has attached input sources or has processed any input. It's an educated guess (I'm not an expert on the run loop internals), but think your refinement is the right way to handle the situation.
Your second example just work around as you poll to check input of the run loop within time interval 0.1.
Occasionally I find a solution for your first example:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]);