Display UI, from a background thread without breaking the flow - objective-c

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
});

Related

Implementing background worker - Obj C/Cocoa

I have 2 windows in my cocoa app. Main window opens a sub window. On click of OK on the sub window, I invoke a deligate on main form which will tell that OK button is clicked on the sub window.
Now, I need to run a long running process on the main window "in the background" so that the window will not become unresponsive. I also have progress bar which should show progress of this long running process.
Please let me know, what is the best way to achieve this.
You should start with Apple's Concurrency Programming Guide.
Specially the section about NSOperationQueue.
You can use Grand Central Dispatch for this. First you create a dispatch queue which will contain operations you want to perform on another thread. Each operation is represented as a Objective-C block (closure).
First you get a queue to put the task you want to run on another thread.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Then you place a block representing the work you want to do on this queue:
dispatch_async(queue, ^{
// this happens on separate thread
NSImage *image = produceImageFromSomeReallySlowOperation()
dispatch_async(dispatch_get_main_queue(), ^{
// this happens on main thread
[myView setImage:image];
});
});
The dispatch_get_main_queue() function returns the queue which is used for operations on the main thread (where the GUI is executed). This means that [myView setImage:image] will be executed on the main thread. You can place your update of the progress bar here. Just dispatch on the main queue to update the progress at every point in your algorithm where it makes sense to do so.
All of this can also be performed with NSOperation which provides a higher level Objective-C interface to the same functionality. But using GCD directly is sometimes easier. It depends on what you want to do.

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

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

Code execution in objective c

HI all, if i've somthing like this:
my code....
// active indicator activity
[otherClass method]; // method that takes 5-6 seconds
// disable indicator activity
my code...
When the long method is called, in my class code is blocked right?
If i active an indicator activity before call the method, it will be animating while "method" is executing?
Thanks.
As iceydee mentions, the UI elements (like your activity indicator) run on the main thread. If you load a big file, download something or any other thing that takes time, you must execute that on other thread if you want to animate UI elements. You can use Grand Central Dispatch, performSelectorInBackGround or other techniques (not recommendable). I would make:
my code....
// active indicator activity
[otherClass performSelectorInBackground:#selector(method) withObject:nil]; // method that takes 5-6 seconds
my code...
Then in otherClass's method, stop the activity indicator on the main thread:
[activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
You should avoid blocking the main thread for that long, consider breaking the method into two - running [otherClass method] in a separate thread. The main thread is used for UI updates, unsure if the indicator will be able to operate with main thread blocked, I think not.
Yes, it will be blocked unless you run your long method in another thread.
To do this use a technique like this. see performSelectorInBackground and performSelectorOnMainThread.