I have an app that makes moderate use of NSURLConnection. These async calls eventually finish and release properly (it looks like), but sometimes it takes some time for them to finish.
So, there are times when I exit the app, (note, not just sending it the background), that some of these connections are still active. If I immediately restart the app, the app freezes on startup. (didFinishLaunchingWithOptions never seems to get called).
While I'm not certain these connections are the issue, it would probably be good to terminate or cancel any remaining. Any suggestions on how to do this?
Bonus points on how to debug the restart also. (I'm already saving NSLog statements to a downloadable file)
You can cancel any NSURLConnection by sending it a cancel command.
[connection cancel];
From Apple docs
Cancels an asynchronous load of a
request. Once this method is called,
the receiver’s delegate will no longer
receive any messages for this
NSURLConnection.
Your start up issue could be related but hard to tell without knowing what type of data you're downloading and how you are using it.
Make sure you are calling release on the connection. Maybe it doesn't call shutdown or close on the socket until the last reference is dropped. I do something like this with no issues with references.
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection release];
[request release];
For cancel last request : [NSURLConnection cancelPreviousPerformRequestsWithTarget:self];
For cancel any specific request : [request cancel];
Note: Here request is the instance of NSURLConnection
Related
I have an iOS 5 app that uses NSURLConnection to load some XML via GET. On very rare occasions connections appear to get stuck in a condition where they timeout repeatedly.
An example request:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:url]];
/*
The request is set with a timeout interval of 10 because (due to the nature of
the app and the XML feed) this data is reloaded every 15 seconds.
*/
[request setTimeoutInterval:10];
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[request setHTTPMethod:#"GET"];
self.afOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
self.afOperation.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
self.afOperation.failureCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
//snip success/completion block code
[self.afOperation start];
So far I've seen three "recovery" scenarios when the requests begin to hang.
Quit the entire app
Plug the device into a computer (yes, really). Right after the iPhone/iPad acknowledges the connection it will immediately stop timing out.
Leave the app and go do something else for awhile. Quickly leaving and reentering the app is typically insufficient to cause recovery.
As you might imagine, I find this incredibly bizarre. At this time I've replaced my own NSURLConnectionDelegate implementation with AFNetworking (as seen above) and am still running into the same problem. I've added logging to every NSURLConnectionDelegate protocol selector and found that the only selector called (after calling start) is connection:didFailWithError:. I've ensured I'm not piling up multiple requests (the previous request is always canceled and nil'd before starting a new one). Additionally, I've verified that no request is actually being sent via tcpdump on my router. What could cause this type of behavior?
It turns out this problem is caused by the TestFlight SDK v1.0 and below. See Why does NSURLConnection fail to reach the backend?
Until they release a fix there's no way to workaround the problem short of stripping out the SDK entirely.
I started seeing the error after installing the testflight sdk, and removing it helped me get rid of it. However, I think it's caused by the interaction between Testflight and ASIHttpRequest (or whichever rest kit you use). It can also be possibly resolved by the following the solution in the link below (disabling compiler optimization on your ASIHttpRequest and ASIFormDataRequest files in your build phases)
https://groups.google.com/forum/?fromgroups#!topic/asihttprequest/fw7PDcD2wKI%5B1-25%5D
I am having a problem tracking the download progress for ASIFormDataRequest.
I can easily get the upload progress but not the download.
In the example on the ASIHTTPRequest site they use ASIHTTPRequest to show download progress.
So is it possible to get the download progress for the ASIFormDataRequest?
I already checked if I get the Content-Length response from the server, dunno what else I am doing wrong.
My setup.
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
request.downloadProgressDelegate = progressIndicator;
[request setShouldAttemptPersistentConnection:NO];
[self performSelectorOnMainThread:#selector(showProgress:) withObject:[NSNumber numberWithInt:0] waitUntilDone:NO];
[request startSynchronous];
The problem with the download progress for ASIFormDataRequest was that I didn't take into consideration the time server is processing the request.
When the ASIFormDataRequest reaches the server it waits until the server responds with the data. If you have to wait for server to perform some kind of calculation this period may take some time.
In my case the upload was super fast as well as the download, hence I could not see the progress, it was always 0 to 100%. The only process that was long and that got me confused was the calculation process the server was performing which in my case is undeterministic.
In short, all was working well, except my understanding... It happens all the time :-)
I would like to start a server request, you can cancel.
My idea is to start the request in a thread so that the user interface does not freeze. So you can kill the whole thread including the request with a click on a "Cancel"-button.
With Android it works: the server request gets started in a "AsyncTask" and in the "onReturn()"-method I can react as soon as the server request finish.
How can I implement this using Objective-C on iOS?
My first attempt was a "NSInvocationOperation". You can cancel the operation, but it's difficult to handle when a request is completed and results are available. I think NSInvocationOperation is not the solution for my issue.
The would you recommend to me? Is NSThread the right choice for me?
Thank you very much!
Note!
This extremely old answer is now here only for historic purposes.
The wonderful ASIHttpRequest library no longer exists; technology is totally different now.
It is unbelievably simple to do this with ASIHttpRequest.
(Asynchronous is so simple, there is no reason you would ever do it not-asynchronously.)
Here are some rough extracts that might get you started.
...
ASIFormDataRequest *request;
...
NSURL *url = [NSURL URLWithString:#"https://blah.blah/blah.cgi?blah"];
request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:#"fred" forKey:#"username"];
[request setPostValue:#"flint" forKey:#"passie"];
[request setPostValue:#"stone" forKey:#"town"];
// send up data...
[request setData:[NSData dataWithBytes:blah length:blah] forKey:#"thefile"];
// or perhaps something like...
[request setData:imageData withFileName:#"blah.png"
andContentType:#"image/jpeg" forKey:#"photoimage"];
[request setDelegate:self];
[request setDidFinishSelector:#selector(postingDone:)];
[request setDidFailSelector:#selector(postingDoneProblem:)];
[request startAsynchronous];
...
-(void) postingDone:(ASIHTTPRequest *)request
{
// it worked
}
-(void) postingDoneProblem:(ASIHTTPRequest *)request
{
// failed
}
Couldn't really be any easier. You're basically just typing out the fields and values.
Per your question, here is how you cancel an "in-flight" request... just set the delegate to nil and then "cancel" it.
[myRequest setDelegate:nil];
[myRequest cancel];
[myRequest release];
ASIHttpRequest is the "miracle library". If you are new to iOS, ASIHttpRequest is simply THE most used 3rd party library. Essentially, every single iPhone app of the 300,000 iPhone apps uses it.
If at all possible BE SURE to donate a few bucks to the guy -- if he stops supporting that library, 100,000 iPhone programmers are buggered!
the documentation is trivial, a child can follow it:
http://allseeing-i.com/ASIHTTPRequest/How-to-use
"Creating an asynchronous request"
it is probably - almost certainly - the most amazingly simple networking library on any platform. It is trivial to do what you describe, happily. Enjoy.
NSURLConnection is async by default and supports cancelation as well as delegate methods when connection has been established, data has been received or whole request has been completed.
Also data transfer takes place in background so that UI stays responsive.
Cocoa's built-in async networking code is not thread-based but works with run loop events, but the result (asynchronous connections) is the same.
Create an NSURLConnection with +[NSURLConnection connectionWithRequest:delegate:]. The delegate you set will be informed about the progress of the connection and can cancel it anytime with -[NSURLConnection cancel].
Check out ASIHTTPRequest, specifically, the ASINetworkQueue subclass, which is described as:
ASINetworkQueue
A subclass of NSOperationQueue that
may be used to track progress across
multiple requests.
I've only used ASIHTTPRequest for a single synchronous request to download directly to disk, which was easy to implement, but I've heard good reports of using queues to manage multiple asynchronous server requests at once.
One thing to note on the recommendations to use +[NSURLConnection connectionWithRequest:delegate:] is that it should only be called from the main thread as of iOS 4.
see http://blog.mugunthkumar.com/coding/ios4-issue-nsurlconnection-and-nsoperation/ for an example of how to deal with this.
I am working on an application where the user at some point must wait for a proper response from a webservice. This might take some time, because it requires a manual confirmation from the webservice. Because of this, the timeoutInterval on the request is set very high to prevent it from terminating too early.
The problem comes when starting the application immediately after the program has returned to the home screen. The application will not start (black screen), and I think it is because the request hasn't been released and is still waiting for a response (I might be wrong though).
I tried the applicationWillTerminate method, but it isn't called when pressing the home button. Again, this might be because the application is still waiting for a response, but a better explanation would be greatly appreciated :)
Also, does someone have any idea on what to do?
code:
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:urlAdress]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10000];
NSHTTPURLResponse* response = nil;
NSError* error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:theRequest
returningResponse:&response
error:&error];
Making a synchronous request on the main thread will block the entire run loop until you receive a response or the request times out. Nothing will get called during this time, including applicationWillTerminate. It is a very bad idea to do this. You should either detach a separate thread for the request or create an asynchronous NSURLConnection.
That being said, the OS will free all memory anyway when it terminates your app so there is little chance that anything from the last launch will be "left over" upon relaunch. Unless your server is blocked because it lost its client when your app terminated. In which case you have to write your server in a way that can handle such a case because it can happen anytime.
If the applicationWillTerminate delegate method is not called,
Then I guess the following delegate method would be getting called.
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(#"Application Did Resign Active");
}
I'm following along with this useful looking answer to my question.
It seems to be working, but here are two questions I have:
How do I detect an HTTP error? The didFailWithError method doesn't seem to be getting called?
UPDATE: It considers any response a success. So I guess I have to handle HTTP errors in the didRecieveResponse method, but besides telling the user there was an error when I hit an HTTP error, do I need stop the connection somehow? And/or cleanup?
I see this line in the answer:
[[NSURLConnection alloc] initWithRequest:request delegate:self];
Do I need to release that? Where, how, when?
You will get the status code returned in the didReceiveResponse
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSHTTPURLResponse *httpResponse;
httpResponse = (NSHTTPURLResponse *)response;
int statusCode = [httpResponse statusCode];
//statusCode will be the http code returned like 201,500
}
For stopping the connection, use a class-level variable for the connection. The best way to go about it would be create a wrapper which sends requests and receives response. Make your viewcontroller a delegate of this class and whenever the didReceiveResponse gives an error status code, call the appropriate method of the delegate and stop the connection.
Here's a good wrapper class example
http://kosmaczewski.net/projects/objective-c-rest-client/
Yes, you need to release that object. See the Memory Management Programming Guide for Cocoa. Basically, if you ever create an object with a method name that begins with alloc or new or contains copy, you become an owner of the object and are responsible for freeing it later. Only in the case where you know you're going to need the object up until program termination is it ok not to free it, in which case the operating system reclaims the memory when your app terminates.
If you only need the object within a small scope, you can send it the autorelease message. This will add it to the autorelease pool. The autorelease pool periodically sends a release message to each object in it. It's kind of complicated; see the section on autorelease pools. For example:
In this case, though, because NSURLConnections are asynchronous, autoreleasing won't work. You don't know exactly when it's going to call back into your object with data, so you want to make sure the object hasn't been released yet. The only way to be sure is to know exactly when you're done with the object, and then send it a release message yourself.
All of the various init* functions return a pointer to the given object, so you can just do:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
...
// when done with connection:
[connection release];
Answering the question in your "update"...
Immediately autorelease the NSURLConnection. The connection object is retained by the NSRunLoop (to which it adds itself automatically unless you use the startImmediately:NO constructor). It will get automatically removed from run loop (and hence dealloc'd) on error or finish.