iPhone:The truth about synchronous NSURLConnection

원문:http://www.cocoabyss.com/foundation/nsurlconnection-synchronous-asynchronous/
본문 아래 의 댓 글 은 여전히 볼 만하 다!!
The truth about synchronous NSURLConnection
Posted on 27/04/2011 In this post, I’ll talk about NSURLConnection, the advantages and disadvantages about synchronous versus asynchronous connections. At the end I show a snippet of code on how to use an asynchronous NSURLConnection on a secondary thread into a concurrent NSOperation. If you ever done networking on iOS or Mac OS you surely have used the NSURLConnection class to communicate with a server. So you know there are two ways of doing networking with this class, synchronous and asynchronous, let’s take a look at them. I – Synchronous NSURLConnection Well, there is not much to say about synchronous connections, they are very easy to implement, in fact once we have configured our NSURLRequest, we need one line of code to kick off the connection :
NSURLResponse* response = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:nil]

That’s it, pretty fast and easy, but there are a lot of caveats :
- The most important problem is that the thread which called this method will be blocked until the connection finish or timeout, so we surely don’t want to start the connection on our main thread to avoid freezing the UI. That means we need to create a new thread to handle the connection, and all programmers know that threading is hard.
- Cancellation, it’s not possible to cancel a synchronous connection, which is bad because users like to have the choice to cancel an operation if they think it takes too much time to execute.
- Authentication, there is no way to deal with authentication challenges.
- It’s impossible to parse data on the fly.
So let’s put it up straight, avoid using synchronous NSURLConnection, there is absolutely no benefit of using it.
II – Asynchronous NSURLConnection
Asynchronous connections require a bit more code, but are still relatively easy to implement, starting the connection also require a single line of code :
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self]; // release later
We even have the choice to start the connection immediately if we add the startImmediately parameter.

Then we need to implement some callbacks methods :
-(void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
  _data = [[NSMutableData alloc] init]; // _data being an ivar
}
-(void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
  [_data appendData:data];
}
-(void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
  // Handle the error properly
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
  [self handleData]; // Deal with the data
}

These are the minimum required methods, there are several more, just take a look at the documentation if you want to know more.
It’s clear that asynchronous connections give us more control :
- You don’t have to create a new thread for the connection because your main thread will not be blocked.
- You can easily cancel the connection just by calling the cancel method.
- If you need authentication just implement the required delegate methods.
- Parsing data on the fly is easy.
So clearly we have a lot of more control with this, and the code is really not difficult.
Even better, we don’t have to handle the creation of a new thread, which is a good thing, because you know, threading is hard.
So when we are doing asynchronous networking we can start the connection on the main thread, that’s a big win, but it doesn’t seem clear enough for lot of developers. Regularly on Stackoverflow I see people complaining about delegates methods of an asynchronous NSURLConnection not being called, the answer is simple and always looks like this :
“If you are performing this on a background thread, the thread is probably exiting before the delegates can be called.”
Yes, you should not need to start the connection on a secondary thread, because networking code really don’t use much CPU time. If you feel that your main thread is sluggish, verify that you don’t perform heavy operation on it, like parsing the resulting data of the connection.
If you wonder, it’s actually possible to schedule an asynchronous connection on a secondary thread, but it requires some tricks and as I said in most of the case you won’t gain much, so be careful if you chose to do this as it will bring complexity into your code.
To show you how to do this, I’ll use a concurrent NSOperation, by the way, the documentation of NSOperationQueue contains an error for iOS 4.x, it actually uses Grand Central Dispatch (GCD), so the behavior is the same as what is described for Mac OS 10.6, the operation object, concurrent or not, will be started on a new thread. I filled a rdar against this error.
I will not go on the detail of creating a concurrent operation, there are plenty of resources available on the net.
What’s really interest us is the start method.
-(void)start
{
  [self willChangeValueForKey:@"isExecuting"];
  _isExecuting = YES;
  [self didChangeValueForKey:@"isExecuting"];
  NSURL* url = [[NSURL alloc] initWithString:@"http://url.to/feed.xml"];
  NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20];
  _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; // ivar
  [request release];
  [url release];
  // Here is the trick
  NSPort* port = [NSPort port];
  NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop
  [rl addPort:port forMode:NSDefaultRunLoopMode];
  [_connection scheduleInRunLoop:rl forMode:NSDefaultRunLoopMode];
  [_connection start];
  [rl run];
}

As you saw, the trick is to add an input source to the runloop, that way, it will not exit.
For this method we can thank Colin Wheeler of Cocoa Samurai because I struggled against this a long time, but well, it’s not obvious at all !
Note that you can add a NSTimer or an empty NSPort.
The delegate methods will be called in your operation on a secondary thread, but when you are done or if the connection fail, don’t forget to remove the port and stop the runloop.
III – Conclusion
Well, if you read me until here, you should be convinced to use asynchronous connections, and forget about synchronous ones. They clearly give us more control and possibilities and, in some case can spare us to create new thread.
So I encourage you to move away from synchronous connections, think of them as evil.

좋은 웹페이지 즐겨찾기