In the CocoaXMLParser class of Apple's CocoaXMLParser example, the following code appears:
rssConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
[self performSelectorOnMainThread:#selector(downloadStarted) withObject:nil waitUntilDone:NO];
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
According to the NSRunLoop documentation "In general, your application does not need to either create or explicitly manage NSRunLoop objects. Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed." In the context of this, why is the run-loop explicitly managed in this example? Would it not be created and destroyed automatically by the thread generated by the NSURLConnection request?
In that code, the run loop is basically just being told to run forever, so that that thread can continue to process incoming background data from the NSURLConnection. Even though a run-loop is created for you, by default the thread would terminate when that method ended.
In general when doing something like that it's easier to put everything in an NSOperation which then goes in an NSOperationQueue (although if you are implementing NSUrlConnection callbacks you have to provide a few extra methods in the NSOperation class).
Related
I've implemented a NSOperationQueue for each file upload to dropbox. I am setting the delegate for each operation that operation's thread. I do not get a callback to any of the delegate methods.
This same procedure works fine if use the main thread.
The problem is that dropbox delegate is registered in another thread. I am using a NSOperation w/ NSQueue I just continue to poll that the operation is not complete.
while (_state != DropboxOperationStateFinished)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
I am currently trying to have a time out of 20 second when making an async request.
The issue am having is that the NSURLconnection runs on the main thread and therefore, if I run an NSTIMER to count the number of seconds that has passed, it never fires the selector since the NSURLconnection is blocking the main thread. I can probably run the NSURLconnection on a different thread since it is thread safe but I have weird issues with my delegates not being called etc.. any help is appreciated.
Below is my sniplet:
NSURL *requestURL = [NSURL URLWithString:SERVER];
NSMutableURLRequest *req = [[[NSMutableURLRequest alloc] initWithURL:requestURL] autorelease];
theConnection = [[NSURLConnection alloc]initWithRequest:req delegate:self];
if (!timeoutTimer) {
NSLog(#"Create timer");
timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:TIMEOUT target:self selector:#selector(cancelURLConnection) userInfo:nil repeats:YES];
}
The asynchronous methods of NSURLConnection do not block the main thread. If your timer isn't firing, this has other reasons. Your problems with using it on a background thread result from the fact that a background thread doesn't have a runloop by default.
This is a great tutorial on how to set up a simple NSOperation to run a method on a separate thread. I'd start with this based on what you have mentioned. Hope that helps!
I have used NSOperationQueue in my iPhone app before in iPhone OS 3.0, but now in iOS 4.0 the code is not working properly. It runs properly only once and on all subsequent calls, it doesnt work. Have there been changes in NSOperationQueue in iOS 4.0?
The relevant code is as follows:
- (void) starteffectFunction {
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(starteffectProcessing)
object:nil];
[queue addOperation:operation];
[operation release];
[queue release];
[spinner startAnimating];
}
-(void) starteffectProcessing{
some code executes. code snippet. A
......
this code is note supposed to execute before A completes. But this executes before A.
}
You are creating an NSOperationQueue, adding an operation to it, then releasing the queue. This is not how NSOperationQueues were designed to work. An NSOperationQueue is supposed to persist, with you adding operations to it as necessary.
This is probably failing because you are deallocating the NSOperationQueue before it has a chance to fire off a thread for your operation. Perhaps on the older OS versions it was just able to do this due to some timing quirk.
I recommend allocating the effect processing queue when you first need it, or in the initialization of your controller object, then keeping that queue around as an instance variable of your controller object. This queue would be deallocated at the same time as your controller object, but you will probably want to cancel all current operations at that time and use NSOperationQueue's –waitUntilAllOperationsAreFinished method to make sure that you are completing all work before deallocation.
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.