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.
Related
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.
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
});
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().
I'd like to create a View/Window that performs some inital loading in my application. I tried something like this:
StartWindow *start = [[StartWindow alloc] initWithNibName:#"Start" bundle:nil];
self.startWindow = start;
[start release];
[window addSubview:startWindow.view];
And in the viewDidLoad event inside StartWindow for the time being I just have [NSThread sleepForTimeInterval:3.0]; to simulate my loading.
The problem is that my view doesn't display until after the thread finished sleeping. why?
Edit
The above code is inside didFinishLaunchingWithOptions.
Because the framework is waiting for you to finish initialising the view in viewDidLoad. If you want loading to happen in the background you have to use some kind of background processing facility, like a separate thread or NSOperationQueue.
BTW, sleepForTimInterval doesn't actually run in a separate thread. It makes the calling thread sleep (in this case, the main UI thread).
The problem is that you block the main thread and thus the OS can't refresh the window and display your new view.
You could try to perform the loading in a second thread, or, if you need to call a lot of non threadsafe functions, you could start the loading after a short amount of time via an NSTimer so that the OS has time to refresh the window.
Another way is to perform the loading in viewDidAppear: which gets invoked when the view is displayed while viewDidLoad gets invoked when the view got loaded from the nib file.
following is .m code:
#import "ThreadLabAppDelegate.h"
#interface ThreadLabAppDelegate()
- (void)processStart;
- (void)processCompleted;
#end
#implementation ThreadLabAppDelegate
#synthesize isProcessStarted;
- (void)awakeFromNib {
//Set levelindicator's maximum value
[levelIndicator setMaxValue:1000];
}
- (void)dealloc {
//Never called while debugging ????
[super dealloc];
}
- (IBAction)startProcess:(id)sender {
//Set process flag to true
self.isProcessStarted=YES;
//Start Animation
[spinIndicator startAnimation:nil];
//perform selector in background thread
[self performSelectorInBackground:#selector(processStart) withObject:nil];
}
- (IBAction)stopProcess:(id)sender {
//Stop Animation
[spinIndicator stopAnimation:nil];
//set process flag to false
self.isProcessStarted=NO;
}
- (void)processStart {
int counter = 0;
while (counter != 1000) {
NSLog(#"Counter : %d",counter);
//Sleep background thread to reduce CPU usage
[NSThread sleepForTimeInterval:0.01];
//set the level indicator value to showing progress
[levelIndicator setIntValue:counter];
//increment counter
counter++;
}
//Notify main thread for process completed
[self performSelectorOnMainThread:#selector(processCompleted) withObject:nil waitUntilDone:NO];
}
- (void)processCompleted {
//Stop Animation
[spinIndicator stopAnimation:nil];
//set process flag to false
self.isProcessStarted=NO;
}
#end
I need to clear following things as per the above code.
How to interrupt/cancel processStart while loop from UI control?
I also need to show the counter value in main UI, which i suppose to do with performSelectorOnMainThread and passing argument. Just want to know, is there anyother way to do that?
When my app started it is showing 1 thread in Activity Monitor, but when i started the processStart() in background thread its creating two new thread,which makes the total 3 thread until or unless loop get finished.After completing the loop i can see 2 threads.
So, my understanding is that, 2 thread created when i called performSelectorInBackground, but what about the thrid thread, from where it got created?
What if thread counts get increases on every call of selector.How to control that or my implementation is bad for such kind of requirements?
Thanks
how to update UI controls in cocoa application from background thread
Simple: Don't.
How to interrupt/cancel processStart while loop from UI control?
Outside of processStart, set a flag variable. Inside of processStart, check that flag and exit the loop if it is set.
Don't try to “kill” a thread from another thread. It's always a bad idea. Tell the thread it's time to stop by setting the flag, and have the thread check that flag and stop at an appropriate time.
I also need to show the counter value in main UI, which i suppose to do with performSelectorOnMainThread and passing argument. Just want to know, is there anyother way to do that?
Yes.
When my app started it is showing 1 thread in Activity Monitor, but when i started the processStart() in background thread its creating two new thread,which makes the total 3 thread until or unless loop get finished.After completing the loop i can see 2 threads. So, my understanding is that, 2 thread created when i called performSelectorInBackground, but what about the thrid thread, from where it got created?
Profile your app using Instruments or Shark and look. It's probably the heartbeat thread for the progress indicator.
What if thread counts get increases on every call of selector.How to control that or my implementation is bad for such kind of requirements?
Every performSelectorInBackground:withObject: message starts a thread. If your thread count isn't going down, it's because your thread method didn't exit. If your thread count is too high, it's (probably) because you started too many threads.
There is a much better way to do this.
First, the general rule in Cocoa is never sleep. Think of this as special ultra-caffeinated Cocoa. For anything you might sleep for in another framework, there is almost always a better, usually easier, way in Cocoa.
With that in mind, look at processStart. All it does is do something every centisecond. How best to do that?
Cocoa has a class for this specific purpose: NSTimer. Create a timer that sends yourself a message at the desired interval, and respond to that message by updating the progress bar—that is, your timer callback method should essentially just be the loop body from processStart, without the loop.
By the way, 100 updates per second is overkill. First off, the user does not care that you have made 1/5th of a pixel's worth of progress since the last time you updated the bar. Second, the screen only updates about 60 times per second anyway, so updating anything visible faster than that is pointless.
- (void)dealloc {
//Never called while debugging ????
[super dealloc];
}
Assuming you put your app delegate in the MainMenu nib, the application object owns it because of that—but it doesn't know that, because it only knows about the app delegate as its delegate, which is a non-owning relationship. (And even if it were an owning relationship, that would just be two ownerships, of which the app would release one, which wouldn't help.)
However, the lifetime of the app delegate doesn't really matter. Its purpose as the delegate of the application means that it needs to last about as long as the application does, but when the application goes away, the process is exiting, which means the delegate will be deallocated as well, as part of the reclamation of the process's memory space. That's why dealloc isn't called—the whole process space goes away at once, instead of objects being deallocated one at a time.
So, in principle, yeah, the app delegate not getting explicitly cleaned up is kind of dodgy. In practice, don't put any temporary-files clean-up in its dealloc (use applicationWillTerminate: instead) and you'll be fine.
I typically work around the problem by putting all my real work in one or more other objects which the app delegate owns. The app delegate creates these other controllers in applicationWillFinishLaunching: and releases them in applicationWillTerminate:, so those objects do get dealloc messages. Problem solved.