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.
Related
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.
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!).
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];
}
Is anyone else having this problem with ASIHTTPRequest? It seems that when I perform an async request from within a background thread with delegate set to the instance I can run into trouble as the delegate can be freed before the request (which is put into an NSOperationQueue) returns a callback.
It seems that ASIHTTPRequest doesn't retain it's delegate - on the other hand Apple's NSURLConnection does retain the delegate ("NSURLConnection retains its delegate when it is initialized. It releases the delegate when the connection finishes loading, fails, or is canceled.").
Should I make sure to perform synchronous ASIHTTPRequests in background threads to make this work (instead of async requests)? Or maybe I should dump ASIHTTPRequest? Or am I just crazy?
Assuming you're using a very recent version of ASIHTTPRequest, the correct way to work with it (and avoid crashes) is that:
The delegate should retain the request (and the request should not retain the delegate)
The delegate should do the following when the delegate is destroyed (or when you want to cancel the request):
[request setDelegate:nil];
[request cancel];
[request release];
You shouldn't get any crashes this way. (I rewrote the delegate handling in ASIHTTPRequest a few months ago exactly to avoid some of these issues, and I checked with the folks from Apple that this was a correct way to handle things before doing so. My changes are all in the official ASIHTTPRequest repository on github, though there hasn't been an official release since - ie. these changes aren't in the v1.7 release, so with v1.7 or earlier you could still see crashes when following the above advice.)
I don't know if ASIHTTPRequests doesn't retain the object, but did you try to retain it when performing and the releasing it at the end ?
I never had a problem yet with this very good wrapper ^^
I have figured out how all of the StoreKit stuff works and have actually tested working code... however, I have a problem.
I made my "store" layer/scene the SKProductsRequestDelegate. Is this the correct thing to do? I get the initial product info like so:
SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: productIDs];
[productRequest setDelegate: self];
[productRequest start];
The problem is that if I transition to a new scene when a request is in progress, the current layer is retained by the productRequest. This means that touches on my new scene/layer are handled by both the new layer and the old layer.
I could cancel the productRequest when leaving the scene, but:
I do not know if it is in progress at that point.
I cannot release it because it may or may not have been released by the request delegates.
There has got to be a better way to do this. I could make the delegate a class external to the current layer, but then I do not know how to easily update the layer with the product information when the handler is called.
OK, problem solved.... ergh.
I went ahead and made the store a separate class, and solved the issue of callbacks to the scene by adding a delegate to the class, which holds the layer of the Store interface. When the transactions finish, I can use the delegate to call back to my scene/layer.
I solved the issue of not knowing if the delegate has been released by using the respondsToSelector: method before attempting to send a message to it.
It turns out the real bug was caused by my attempt to fix 1 & 2 in the first place. I overrode onExit to let me know when to remove the class as the store delegate. It turns out I forgot to call [super onExit], which is where the scene is released. Hence, it stayed retained and did not get removed from the touchHandler. Oops!