I have an iphone app, it run some thread to compute search. The search is made calling a time consuming function from a library.
I need to exit from the thread when the app is terminating, otherwise the thread continue to run and the search create problem when i reopen the app.
I tried to subscribe in the thread
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mainApplicationWillTerminate) name:#"UIApplicationWillTerminateNotification" object:nil];
And in mainApplicationWillTerminate
-(void)mainApplicationWillTerminate;
{
[NSThread exit];
}
The problem is still present, any idea?
As stated in the docs you should avoid using [NSThread exit]. In general, to avoid memory leaks and other disasters, a thread should never be stopped "from the outside". A thread should always exits by itself.
In your thread main loop you should check if the thread was cancelled:
if ([[NSthread currentThread] isCancelled]) {
return;
}
To cancel it you call its "cancel" method from another thread.
In your case you should setup an application delegate (see UIApplicationDelegate)
- (void)applicationWillTerminate:(UIApplication *)application
{
[myThread cancel];
}
Better have a look to the nice NSOperation class also.
Related
I'm having trouble understanding an Exception, which is thrown a couple of seconds after the home button is pressed.
*** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[GMMLoader performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
I'm programing an application, which uses a webservice to load different datasets. The webservice is added to an operationQueue.
WebService* op =
[[WebService alloc] initWithSearchString:self.searchBar.text
notificationCenter:self.progressNotification];
[self.queue addOperation:op];
[op addObserver:self
forKeyPath:#"isFinished"
options:NSKeyValueObservingOptionNew
context:NULL];
[op release];
In the -(void) start message of the web service, I call this start selector to be performed in the main thread, to be able to interact with the UI.
- (void)start
{
if (![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:YES];
return;
}
...
}
All of this works fine, but when I press the home button while the web service is active, I get the mentioned exception.
My guess is, the main thread is halted after the home button was pressed (Is that so?). So the action which is currently running is no longer updated / valid.
I tried to listen to the event UIApplicationWillResignActiveNotification to react on the home button press by canceling or suspend the operations in queue. However this did not help. Am I right with my guess, that this error occurs because of the halted main thread?
How else could I manage to stop / cancel the current webservice thread?
Thank you in advance
Zerd
I found the error.
As it turns out, it was not a problem with the webservice itself, but with a geolocator, which was created and started twice.
Due to this, some kind of zombie thread was created, which would never stop.
I have an iPad app, and I want to do this
-(IBAction) clicked {
image=download(#"http://....."); // this is on the main thread
}
The download function is going to call a whole bunch of non blocking functions to download a file from the internet, but download itself shouldn't return until the image is downloaded.
While the program is waiting for the download at the image=download(...) line above, I want the UI to be able to still function, for example be able to scroll a UITableView, click another button etc.
So what I did was this inside the download function I used a RunLoop
-(void) download:(NSString *)url
{
BOOL stillDownloading=TRUE;
while(stillDownloading) {
stillDownloading=downloadAFwBytes(...);
CFRunLoopRunInMode(kCFRunLoopCommonModes, 0, YES);
}
}
I thought the CFRunLoopRunInMode function will keep pumping UI messages, touches, scrolls through the main UI thread so that the UI will keep working and not freeze until the download finished, but for some reason, it only works for a short time, and eventually the UI freezes.
Do you know why, or how to fix?
The download function is called everywhere in the program, that expects it to wait for the download, so I can't change it to non blocking at the moment.
The direct answer to your question is, no, this is not what CFRunLoopRunInMode does. What you are effectively trying to do is have the current run loop "yield" so execution can continue while the loading operation continues. This is not how iOS and run loops work. Your download function blocks the thread it is on until downloading is complete so the only solution to your issue is to change the implementation so that downloading occurs on a background thread and the objects that care are notified when it is complete. Here's a relatively small change that can get you on the right track. This overall topic (concurrency, managing background tasks) is a bigger discussion and there are different considerations/tradeoffs. I'll cut to the chase and hopefully get you on the right track.
Define a couple NSNotification's that your download method can post for interested objects to observe:
// in the .h file of the class performing the download
extern NSString * const MyClassLoadingDidStartNotification;
extern NSString * const MyClassLoadingDidFinishNotification;
// in the .m file of the class performing the download
NSString * const MyClassLoadingDidStartNotification = #"MyClassLoadingDidStart";
NSString * const MyClassLoadingDidFinishNotification = #"MyClassLoadingDidFinish";
In your download routine, do the download in the background and post the appropriate notifications:
-(void) download:(NSString *)url
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:MyClassLoadingDidStartNotification object:self];
BOOL stillDownloading=TRUE;
while(stillDownloading) {
stillDownloading=downloadAFwBytes(...);
}
[[NSNotificationCenter defaultCenter] postNotificationName:MyClassLoadingDidFinishNotification object:self];
});
}
In any object that initiates a download, observe and handle the notifications
// in any class that initiates a download
- (void)init...
{
self = [super init...];
if (self) {
// other initialization
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didStartLoading:) name:MyClassLoadingDidStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishLoading:) name:MyClassLoadingDidFinishNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MyClassLoadingDidStartNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MyClassLoadingDidFinishNotification object:nil];
}
- (void)didStartLoading:(NSNotification *)notification
{
// update UI to show loading status (make sure you do UI changes on main thread)
// optionally check notification.object to ensure it's the loader class instance you care about
}
- (void)didFinishLoading:(NSNotification *)notification
{
// update UI to show loading status (make sure you do UI changes on main thread)
// optionally check notification.object to ensure it's the loader class instance you care about
}
Keep in mind that this is a very basic starting point. As you learn more and decide what you need you will definitely customize it. For example, you may want to add error handling, limit concurrent loading operations, provide other loading status, etc.
There are some architecture concerns with your question, but to properly download an image in the background and load it into a UIImageView or use it in any other way, I'd suggest taking a look at AFNetworking and read through their sample code for downloading resources.
Can you request to download the image on the background and then, return a static image asap in your method?
This way, the main thread returns fast and depending on your architecture, your background thread can update the image once it got it?
I think we need more details on your code (your download method is void so how is the downloaded data used?) to be able to really help.
Along with the 3 other guys telling you the same thing, I'll tell you to forget about your own run loop model, and just use the async download capabilities (assuming you're downloading over the net). You don't even have to build a thread, just start the async downloads and they will tell you when they're done. If you don't have the time to code it right, when will you have the time to fix your code?
Not sure if this is solved yet but can't you use?
[self.operationQueue addOperationWithBlock:^{
[self MethodName];
}];
and when you want something to happen in the UI like table updates then put this in the method code:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
////perform something here ////
}];
hope this helps.
I've created an NSOperation in the queue like so:
ImageLoadingOperation *operation = [[ImageLoadingOperation alloc] initWithImageURL:url target:self action:#selector(didFinishLoadingImageWithResult:)];
[operationQueue addOperation:operation];
[operation release];
And this works fine but if the view gets popped before the operation finishes the App crashes with "EXC_BAD_ACCESS"
I've tried to cancel the the operation Queue by calling cancelAllOperations but as its already in process it doesn't prevent the App from crashing. The docos say that if the operation is running it is up to the operation to detect that it has been canceled and respond appropriately but not too sure how I would implement this?
Any ideas?
It is a general problem for View calling some network and then callback.
My solution is you can retain the view before you call the operation. And then, when the operation finishes, you release the view.
- (void)longTask {
[self retain];
}
- (void)longTaskDidFinish {
// do something if you want
[self release];
}
You will have to either override the "cancel" operation in your ImageLoadingOperation class, or have your ImageLoadingOperation add itself as KVO observer to the "cancelled" property. There - you can intelligently cancel your operation in such way that it won't crash.
Also, if your ImageLoadingOperation runs in the background, it would be wiser to defer your access to the views somehow to the main thread (where all drawing takes place). You could use a dispatch_sync(dispatch_get_main_queue(), ^{}); or even performSelectorOnMainThread for actual access to the related view.
You see - the whole point of using an operation queue is to remove dependencies, and let things run in parallel, but your operation must synchronize with the view-system changes, and that must be designed for completeness and robustness.
You could retain the view before the callback of operation is called, as vodkhang mentioned above. But that will prolong the life of the view unnecessarily because since the view is popped you don't want the operation to continue any more.
Here is a sketch about what you should do to respond to the cancel command:
- (void)start{
if(self.isCancelled){
[self markAsFinished];
return;
}
//start your task asynchronously
}
//If you want to cancel the downloading progress immediately, implement your own 'cancel' method
- (void)cancel{
[super cancel];
if(self.isExecuting){
{
......
cancel load process
......
}
[self markAsFinished];
}
}
- (void)markAsFinished{
......
change 'finished' to YES' generate KVO notifications on this key path
change 'executing' to 'YES'; generate KVO notification on this key path
......
}
This sketch is based on ASIHTTPRequest networking library, and
there is an official guide on how you should respond to cancel command.
I have got a memory bug that seems to boil down to something happening in a thread. I am having difficulties troubleshooting this.
I have a UIViewController, that when active, i.e. the user is using its view, retrieves updates from a web service in an NSThread.
This is done every 3 minutes and this delay is controlled by a:
[self performSelector:#selector(timerDone) withObject:nil afterDelay:180.0];
The timerDone method now starts the NSThread that retrieves the web service data and also it sends the performSelector message again. This is a little "check for updates, populate views, shut everything down, repeat" routine that works just fine.
Now, the user can of course suddenly tap a button an load up a second UIViewController. When this happens I call:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(timerDone) object:nil];
And do my cleaning up in the dealloc method.
My question is now: What happens if the NSThread was running while the user changed the view and set in motion the deconstruction of this object that is the starting point of the NSThread?
Should I keep a BOOL around that tells me if the NSThread is still active, and if so, what to do with the NSThread if this is the case.
The threading is done like this:
- (void) runTimer {
[self performSelector:#selector(timerDone) withObject:nil afterDelay:180];
}
- (void) timerDone {
[self performSelector:#selector(runTimer) withObject:nil afterDelay:2];
[NSThread detachNewThreadSelector:#selector(updateAllVisibleElements) toTarget:self withObject:nil];
}
- (void) updateAllVisibleElements {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//call approiate web service
[pool release];
}
You have two problems here: first, you're using performSelector:withObject:afterDelay: to do what an NSTimer does best (periodic callback). cancelPreviousPerformRequestsWithTarget:selector:object: can be quite expensive, and because of your threading is likely creating race conditions.
Second problem: each thread has its own run loop, and both mechanisms (performSelector:... and NSTimer) and are tied to the current thread's run loop.
Here's what I recommend: Create a single, long-lived NSThread with its own explicit run loop for all your update needs. Look at the Threading Programming Guide for some good example code of this. On that thread, set up a 3-minute repeating NSTimer. Every 3 minutes, update.
If you need to schedule an update outside the three-minute cycle, then you use performSelector:onThread:withObject:waitUntilDone: to call your updateAllVisibileElements. The way I generally do this is to encapsulate all of the thread logic into a single object (WebServiceController or whatever). It creates it own NSThread and saves it in an ivar. Then I use code like this:
- (void)requestUpdate
{
if ([NSThread currentThread] != self.thread)
{
[self performSelector:#selector(update) onThread:self.thread withObject:nil waitUntilDone:NO];
return;
}
else
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//call approiate web service
[pool drain];
}
}
One more note: you mention that the background thread "populates views." A background thread should never call into UIKit. UIKit is not thread safe and should only be called on the main thread. I typically achieve this by posting notifications onto the main thread which the view controllers observe. The "updating" object should not know anything about the UI. That breaks the Model-View-Controller paradigm of Cocoa.
I am trying to perform an operation on a background thread. In the past i have used
[self performSelectorInBackground: #selector (getSetDisplay) withObject: nil];
to accomplish this. However, I have registered for this function to be executed within an NSNotification and I need to animate the UIActivityIndicator. The UIActivityIndicator (from what I understand) needs to run on the main thread, but this notification is doing that as well and is blocking it.
- (IBAction) btnRefresh_clicked :(id)sender{
[activity startAnimating];
[navigationUpdateFromDetail setUpdate: NO];
[navigationUpdateFromDetail.locationManager startUpdatingLocation];
[[NSNotificationCenter defaultCenter] addObserver: self selector : #selector (getSetDiplay) name: #"LocationUpdated" object: nil];
}
does anyone have an idea about how to get this running on a background thread? thanks is advance.
Take a look at the NSObject method performSelectorOnMainThread:withObject:waitUntilDone: - it lets you run a method, like startAnimating, on the main thread while still keeping your currently executing method in the background.