The truth about synchronous NSURLConnection

In this post, I'll talk about NSURLConnection, the advantages and disadvantages about synchronous versus asynchronous connections. At the end I'll 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 the 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 requires 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 doesn'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 choose 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 fails, 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, just think of them as evil.


IV - Old comments


Peter
One big disadvantage of asynchronous connections is that they require a run-loop.
Imagine your app is about to be terminated and the appropriate callback is called. You only have till the end of the function to upload your changes. An async transfer won’t finish. Thats something to keep in mind, especially when designing an API (Hello dropbox!)
Also synchronous connections make code that is much, much easier to read.
I’ve had a good experience with blocks. It makes dispatching a thread and calling back so easy. For most situations I now shy away from async APIs.

Nyx0uf
Runloop is not a disadvantage, you have one runloop per thread, using it is not evil at all.
Plus sync connections use a runloop, you just don’t see it, that’s what make it easier to use perhaps.
For code readability, I don’t agree because spawning a thread is not easier to read, except if you use blocks and GCD.

Read this nice topic on the Apple’s devforums : https://devforums.apple.com/message/37677#37677

Even Apple engineers discourage the use of synchronous API

DAloG
Why not use synchronous request in background thread? Simple thread managment solve many problems that are described here. And code stay simple) btw, thank you and Colin Wheeler for the nice trick)

Nyx0uf
Quoted from the link I gave above :

1. Resource Usage
—————–
The number one reason synchronous blocking networking is bad is that it wastes resources. You can’t do synchronous blocking networking on the main thread, so you necessarily have to create a secondary thread to do the work. That thread is a waste of resources, most notably:

o virtual address space (always consumed)

o a /wired/ kernel stack (consumed while the thread is blocked)

This is especially bad when you’re handling lots of connections simultaneously, most of which are idle. You consume a bunch of wired kernel stacks to get exactly /no/ work done.


Playing with a runloop is cheaper than spawning a thread.

Christopher Pickslay
Nice post. Another way to keep the runloop form exiting is to just put it in an infinite loop (thanks to Wim Haanstra for the idea: http://www.depl0y.com/?p=345):

while(!isFinished) {[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}

The thing I like about this approach is that it’s self-contained–you don’t have to remember to clean up the runloop when the operation finishes.

Wayne Lo
Very nice program. One question: if for some reason the code need to exit while the image is still in the loop waiting to download the image, is it necessary to call cancel and unscheduleFromRunLoop for the NSURLConnection object in order to prevent crash?

Nyx0uf
If you don’t unscheduleFromRunLoop you might en up with a thread running but un-reachable, so a big waste of resources !

ted
Similarly to using asynchronous NSURLConnection there are quite some advantages of using NSTask in an asynchronous way as well.

asynctask.m is a command-line tool that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask. It creates its own NSRunLoop, uses asynchronous “waitForDataInBackgroundAndNotify” notifications and pthread_create(3) and pthread_detach(3) for writing more than 64 KB to the stdin of an NSTask.

see:

http://www.cocoadev.com/index.pl?NSTask

http://www.cocoadev.com/index.pl?NSPipe

Jojas
Very nice code. so , May I ask some question.
Is it possible to make pararell connection with NSRunloop?
or should I make some Manager Code to manage each task?

Nyx0uf
If I understand correctly, you want to schedule one or more NSURLConnection on the same NSRunLoop ?

It should work, like if you are using asynchronous connections on the main thread.

Jojas
That’s right ! now I can made it
but it need to write some Queue Management Code for handling each response data.

so it’s okay for me

Mark
Hi, i´m using synchronous connection becouse I need to wait (block the UI) for the information to display it. My info is on a Servlet and I need to “download” to show it on UI

The only way to do that with asynchronous connection is using a” Do While False loop” or something like that. I don´t like that kind of code. Is ugly.

That is the reason to use synchronous connection.

Do you know a clean/good way to do that with asynchronous connection??

Thanks and sorry for my poor english

Nyx0uf
Blocking the UI is wrong.

Download your data in the background, and update your UI on the main thread once you have them.

I don’t see where you need a do..while loop with an asynchronous NSURLConnection…

Mark
I give you my example:

I have a list of credit cards. When I click in one of those, I need to call the servlet and when the servlet give me the data of that card, I have to show the data (Name, Expiration Date, etc).

I can´t show the UI before the data

You understand me?

Thanks you very much

Nyx0uf
You can !

Show the UI and fill it progressively while the data arrive.

Otherwise if you are on a slow network your UI will be frozen until the data are available, that’s a very bad user experience.

Users don’t like to be prisoned in a frozen UI, show them something or at least you must give them the ability to cancel the operation if it take to much time.

kimimaro
since iOS5.0, following method has been added in .
+(void)sendAsynchronousRequest:(NSURLRequest *)requestqueue:(NSOperationQueue*) queuecompletionHandler:(void (^)(NSURLResponse*, NSData*, NSError*)) handler NS_AVAILABLE(10_7, 5_0);
so, need we do the trick
(NSPort* port = [NSPort port];NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop)
introduced above mentioned?

Nyx0uf
No, that’s all the benefit of this method, you simply pass an operation queue and all the boilerplate code is done for you. The inconvenient of course is that it gives you less control.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值