Where is the right place to cancel an ongoing request of ASIHttpRequest? This is how I do my cancellation but it keep on crashing when I transfer from one viewcontroller to another without letting the request to finish. The cancellation fo request works fine, but when switch back to the first Viewcontroller it will crash.
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if(!DID_FINISH_REQUEST)
{
[requestNewReleases setDelegate:nil];
[requestNewReleases cancel];
[requestNewReleases clearDelegatesAndCancel];
}
}
In the ASIHttpRequest documentation there is an example of a request being cancelled in the dealloc method:
Safely handling the delegate being deallocated before the request has
finished
Requests don’t retain their delegates, so if there’s a chance your
delegate may be deallocated while your request is running, it is vital
that you clear the request’s delegate properties. In most
circumstances, if your delegate is going to be deallocated, you
probably also want to cancel request, since you no longer care about
the request’s status.
In the example below, our controller has an ASIHTTPRequest stored in a
retained instance variable. We call the clearDelegatesAndCancel method
in it’s dealloc implementation, just before we release our reference
to the request:
- (void)dealloc
{
[request clearDelegatesAndCancel];
[request release];
...
[super dealloc];
}
It depends on your architecture and application needs - eg. If there are multiple http requests only some of which need to cancel?
Then solution -
for (ASIHTTPRequest *req in ASIHTTPRequest.sharedQueue.operations)
{
[req cancel];
[req setDelegate:nil];
}
This will cancel and remove the delegate of all in progress requests that have been assigned to the default queue.
Related
Hy
I've got a UIView. In one method, I alloc a WebView, and set it's to a retain property.
self->webView= [[UIWebView alloc] initWithFrame:self.frame];
Then I start to load a HTML string. After it's loaded, I resize the view and start a callBack to the superview to resize. It's working.
My problem is that, if the user go back before the view has been loaded, the view's are released. Then my WebView throw a BAD_ACCESS.
Here is the dealloc method:
-(void)dealloc{
[self.webView setDelegate:nil];
[self.webView stopLoading];
[self setWebView:nil];
[htmlStr release];
[super dealloc];}
The callback trace is shown in the screenshot. The interesting is that, if I don't release the WebView it is work's like the charm. If I release the WebView, then when it's deallocated, I get an error message in the log:
![bool _WebTryThreadLock(bool), 0x4e05150: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...][1]
EDIT: It has been fixed. It turns out, that my image loading method was guilty. I've start a new thread (NSOperationQueue and NSInvocationOperation in that) to load an image, and make a performSelectorOnMainThred: when it's finished. Whit that, I've loaded more than hundred small (1-2 KB) image, on every page switch, which was a really big overhead. When I wrote a method which download the images in one thread one by one, this bug has never came again
You are modifying UI from other tread than main. This is forbidden as UIKit is not thread-safe (hence the crash...). If you want to modify the UI from another thread, you must use:
performSelectorOnMainThread:withObject:waitUntilDone:
Other thing I've notice in your code is that you incorrectly release your properties in -dealloc. You should not use synthesized setters like this:
[self setWebView:nil]; // same as self.webView = nil;
You should not, because it can bring you lots of problems if you start using KVO (Key-Value Observing) on you properties... Instead just write:
[webView release];
or, if you want to avoid "The Heisenbug":
[webView release], webView = nil;
EDIT: you can also benefit from answer to this SO question: How to safely shut down a loading UIWebView in viewWillDisappear?
Dont call it with self.webView in dealloc, just use webView:
- (void)dealloc {
if ([webView isLoading])
[webView stopLoading];
[webView setDelegate:nil];
[webView release], webView = nil;
[super dealloc];
}
It has been fixed. It turns out, that my image loading method was guilty. I've start a new thread (NSOperationQueue and NSInvocationOperation in that) to load an image, and make a performSelectorOnMainThred: when it's finished.
Whit that, I've loaded more than hundred small (1-2 KB) image, on every page switch, which was a really big overhead. When I wrote a method which download the images in one thread one by one, this bug has never came again.
I was using NSURLConnection in a synchronous way before running on a background selector, so when I moved over to ASIHTTPRequest I did the same with this framework.
So, is it a bad idea to do something like the following?
// From another method
[self performSelectorInBackground:#selector(callDatasource) withObject:nil];
- (NSData *)callDatasource {
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:someURLthatIamusing];
[request setTimeOutSeconds:50.0];
[request startSynchronous];
NSError *error = [request error];
NSData *returnedData;
if (!error) {
returnedData = [request responseData];
} else {
// do something with error
}
[self performSelectorOnMainThread:#selector(done) withObject:nil waitUntilDone:NO];
[apool release];
return returnedData;
}//end
What would be the advantage to use the ASIHTTPRequest and asynchronous methods along with the delegate methods?
From experience, sometimes odd things can happen when using ASIHTTPRequest synchronous requests off a secondary thread: the download activity icon in the status bar not disappearing upon download completion is one issue I've noticed from time to time. I've had no major problems in the past, but I use the asynchronous methods now rather than your approach. The ASI asynchronous methods are by the nature of being a widely used library more highly tested than my own implementation could ever be.
There are a number of advantages with using the asynchronous methods - you mention the delegate methods, but the latest release of ASI actually also supports blocks, which is a great leap forward (dealing with multiple synchronous calls used to be a bit of a pain due to the shared delegate methods (or unique delegates for each asynchronous call). But with blocks you can now get rid of the delegates entirely. I've found them to be really useful. Plus if you use multiple contributors it can make readability a lot easier.
Also, by doing it Async, you can more easily track progress through the setProgressDelegate command.
I need to make a series of http asynchronous request calls within a for loop . But then i would need to wait for the download to complete for a response before making the next request without crashing. So using NSNotification (or anything else) how can i handle this.
When you do a async request you wont be required to wait. However you need to handle the responses correctly. You can check the ASIHttpRequest library which currently seems to be the favorite of developers on SO.
If you have to wait for the response then you are not doing it asynchronously.
Coming to using NSNotification, a better cleaner and simpler approach would be to use protocols. This way you implement the delegate and send you next request everytime the delegate method is called.
You want to run a number of serial requests in the background and wait for each request to finish before the next gets started. This is no different than running anything else in the background. Here is something I whipped up:
[self performSelectorInBackground:#selector(runRequests) ...];
- (void)runRequests
{
for (int i = 0; i < self.urls.count; i++) {
NSURL *url = [self.urls objectAtIndex:i];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous]; // Note: not asynchronous
// Handle response ...
ALog(#"%# completed", url);
}
[[NSNotificationCenter defaultCenter]
postNotificationName:#"all requests completed!" object:nil];
}
I'm using ASIHTTRequest here, but using NSURLRequest works just as well in this case. The important thing is that the request is run to completion.
You can do it by using threads as follows
[NSThread detachNewThreadSelector:#selector(myMethod1) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(myMethod2) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(myMethod3) toTarget:self withObject:nil];
and so on...........
You can pass your requests in these methods asynchronously.
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.
Before my view loads I call:
[theConnection cancel]; //assume theConnection is an NSURLConnection
I then proceed and make my proper NSURLConnection.
Will calling cancel before a connection is even made cause any problems?
No, the NSURLConnection documentation makes it clear that cancel is possible and that the delegate will receive no further messages once it is called. Also that the delegate is released on cancel.