I have ASINetworkQueue with more than 1500 requests in it. Performing this number of requests takes for a while. If user leaves view controller while this queue is running the OS deallocates the view controller and I get "message sent to deallocated instance" error.
I have tried to use
[self.queue cancelAllOperations];
in dealloc method, but seems like it cancels only requests that are waiting in queue, not the request that is currently running and I'm getting the same error.
What is the correct way to handle this situation? Is it possible to make the view controller not to be deallocated while queue is not finished even if user left it? Or is there a way to cancel all requests (including requests that are running) in queue?
The suggestions by #darvids0n and #AlexReynolds are both good.
The problem is probably that the delegate for the current request is still set, so it tries to notify the (now deallocated) delegated that it's been canceled.
You can most likely avoid this by setting all the delegates to nil instead:
for (ASIHTTPRequest *req in queue.operations)
{
[req setDelegate:nil];
[req cancel];
}
[queue setDelegate:nil];
Detach the network queue's lifespan from that of the view controller. For example, have it managed by the application delegate, which should be alive as long as the application is alive.
When the view controller is dismissed, cancel all operations in the app delegate's network queue. Even if the view controller is dead, the app delegate should keep chugging along, giving enough time for the network queue to do its cleanup work.
If you think you'll have multiple queues, keep references to them in an array or dictionary with some identifier or index so that you can keep track of them separate from their respective view controllers.
Related
I have an application in which I am required to connect to the internet after a view is loaded. However, if I put this code in the viewDidLoad method the parent view freezes, and then unfreezes after the connection onto the new view. However, I would like the new view to load FIRST, and then to start the connection. I tried using viewDidAppear:, however I am getting the same issue.
Also, will any animations continue playing during the connection? Will the UI be responsive? If not, is multithreading the way to go?
Here is some of my code:
-(void)viewDidLoad {
[super viewDidLoad];
//Do some other view initialization
//Connect is a class I use to connect to the internet
[Connect getData:someString];
}
When I put the code in viewDidAppear the same thing happens.
Connection code:
NSMutableURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSHTTPURLResponse *response;
NSError *error;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
Also, I forgot to mention that I am running a regular expression as well after the connection.
As the name of the method says, the view has already been loaded when viewDidLoad executes.
Generally, be sure to use asynchronous connections to connect to the internet. Never block the main thread.
it is easier than you may think.
All you need is some thread management. On the view did Do:
[NSThread detachNewThreadSelector:#selector(yourMethod:) toTarget:yourTarget withObject:yourObject];
and later in another part do:
- (void)yourMethod:(id)sender{
//download the info but do not update the GUI
[self performSelectorOnMainThread:#selector(updatingTheGUI:) withObject:yourObject waitUntilDone:NO]
}
- (void)updatingTheGUI:(id)sender{
//Update your GUI
}
You will notice that the viewDidLoad method documentation of UIViewController states:
...Called after the controller’s view is loaded into memory.
This doesn't necessarily mean that it's called after the view is displayed on screen.
To answer your other questions, if you make your network request the way you have described, no, animations will not continue playing while the request is in progress and no, you can't guarantee that the UI will be responsive. This is because the network request will take an unknown amount of time. Therefore, if you make the request on the main thread, the main thread will be blocked for that period of time, however long it takes.
And, as for the last question, is multithreading the way to go? As others have stated, the easiest and probably most popular way of handling this is to initialize the NSURLConnection with initWithRequest:delegate:. The delegate being your UIViewController or Connect class, or whatever class you want to conform to the NSURLConnectionDelegate protocol and use the NSURLConnectionDelegate methods to process the downloaded data. NSURLConnection will do the work asynchronously and keep the main thread free to handle animations, displaying the UI, etc.
I know it sounds a bad idea for your app. performance but try giving a delay or sleep in between to check if it works that way. Later try to implement the asynchronous call as someone earlier stated..
I am using three20 and implement the model like the example of TTRemoteExamples. Now problem is: when I click and open a page, the TTURLRequest sent out, during fetch data from remote, I click to open another page. But the previous network request is still there messed my loaded data. So I want to know the way to cancel previous network request when I switch to another page. Or when I click button to do a new request in the same page.
Thanks~
To cancel a TTURLRequest, keep a reference to it (typically in an instance variable) then send it a "cancel" message. Like so:
[self.myRequest cancel];
If you don't want the delegate to be notified of the request being cancelled, do:
// I'm assuming self is the delegate here, that may not be true
[[self.myRequest delegates] removeObject:self];
[self.myRequest cancel];
You'll typically also want to do this in your view controller dealloc method. If a request continues after the viewController has been deallocated, it will try to send delegate messages to it, and you'll get a bad access crash.
As for the timing of when you cancel it, that's up to you. If you need it to stop when a user leaves your view controller, then implement UIViewController's viewWillDisappear: or viewDidDisappear: methods (don't forget to call super!).
I'm running an http request with my view controller as the delegate using ASIHTTP, if the user leaves the view controller before the request is done, I get a EXC_BAD_ACCESS in the ASIHTTP code that is doing an if ( delegate ... )
Is there something I have to do in my view controller dealloc? I want to simply drop the request if the user leaves.
The most common reason for problems like this is an ASIHTTPRequest object that is still active and where its delegate field points at an object that is now destroyed. The EXC_BAD_ACCESS will then happen when ASIHTTPRequest tries to tell the delegate the request has finished and so on.
Your dealloc method for the object that is the asihttprequest delegate should have:
request.delegate = nil;
[request cancel]
[request release]
If you have only one request active at a time, you must have this same code anywhere you start a new request, to ensure that any previous request is correctly cancelled.
If you have more than one request active, you will need to keep track of all of them, and cancel and nil the delegate of all of them.
For reference, I've been using ASIHTTPRequest for ~18 months, and have contributed back a number of fixes for difficult race conditions related to cancelling requests.
In my iPhone/iPad app I'm handling all network and web-API-requests through a "APIManager" (singleton, created in AppDelegate).
Currently the APIManager contains only one single ASINetworkQueue, to which APIRequests (subclass of ASIHTTPRequest) are added and executed. In the userInfo of each APIRequest some additional information to handle the request is added (like whether the response should be parsed into Core Data - and if so, which entity - or not).
When the user moves from one view (ViewA) to another (ViewX), I would like to have the possibility to cancel all the requests that ViewA have asked the APIManager to perform, while letting others continue.
Is there some functionality to find a certain request in a ASINetworkQueue (or NSOperationQueue) and send a cancellation message to it? Maybe using parameters added to the userInfo of the request?
Thanks in advance!
[queue operations] will return an NSArray of items in the queue, which you can then iterate and call 'cancel' on any you like.
Something like:
for (ASIHTTPRequest *req in [queue operations])
{
if (shouldCancel(req))
[req cancel];
}
I have a simple query that I'd like cleared up by someone... Is it bad-practice to retain self?
I have a server request object that I'd like to make. I'd like to be able to use it in the following fashion:
ARequest *request = [ARequest request: someParam];
request.delegate = self;
[request begin];
In order for the object not to self destruct as soon as the autorelease pool is drained, I imagine I need to call a retain in it's init method and then a release once the server response has been received, processed and delivered to it's delegate.
However, something is raising a warning bell in my head with this approach. Better ways to do it?
There is nothing wrong with retaining self, as long as you release it at some well-defined point in accordance with normal memory management protocol. If an object requires itself to exist until some condition is met, it should take responsibility for that, in the same way as it does for any other object it requires to continue existing.
Introducing otherwise extraneous manager objects or foisting the responsibility off on the object’s owner for superstitious reasons would be the real anti-pattern here.
(The equivalent approach in garbage-collected code would be for the object to exclude itself from garbage collection while results are pending, or root it through a collection of some sort if you dislike that idea.)
It's not unheard-of, but it is somewhat uncommon. The main way I've seen it used (and used it myself) is when you're dealing with some sort of semi-synchronous object (by semi-synchronous I mean that it does not block the main thread, but it also does not execute on a background thread; an NSURLConnection would fit this bill). For example, I wrote a subclass of NSWindowController that was specifically for displaying a window as a sheet and for invoking some certain delegate callbacks. Basically, you'd alloc/init a new sheet controller and invoke beginSheetForWindow:. This would run the sheet semi-synchronously, and then invoke an appropriate callback when the sheet was dismissed.
Since the invoking object doesn't necessarily "own" the sheet (think of it as a Mac version of a modal view controller on iOS), the sheet controller does [self retain] immediately before showing the sheet, and [self release] immediately after cleaning up and invoking callbacks. The purpose behind this was to ensure that the controller object would stick around until the sheet was done. (The sheet, IIRC, was retained by the runloop, but I also needed the controller to stick around)
Like I said, it's very rare to come across a situation where you would want to [self retain], but it's not impossible. However, as a general rule of thumb, if you think that you need to [self retain], you may want to think again.
Easiest way to do this would be to create an iVar for your request, retain the request when you start it and release it when the last delegate method is called.
Is ARequest a class you created? Does it create a new thread to asynchronously submit the request?
I once did the same thing as you. I wrote a Category-Method on NSString to send it it to a server, that will print it. In the Category-Method I had to call [self retain], so that the callback methods could be a NSString-Categroy-Method to.
I felt so bad about it, that I rewrote everything to use a Singleton, that is accessed by the Category-Method. So the Singleton will retain the string as long as necessary.