views:

7777

answers:

7

I'm in the design stage for an app which will utilize a REST web service and sort of have a dilemma in as far as using asynchronous vs synchronous vs threading. Here's the scenario.

Say you have three options to drill down into, each one having its own REST-based resource. I can either lazily load each one with a synchronous request, but that'll block the UI and prevent the user from hitting a back navigation button while data is retrieved. This case applies almost anywhere except for when your application requires a login screen. I can't see any reason to use synchronous HTTP requests vs asynchronous because of that reason alone. The only time it makes sense is to have a worker thread make your synchronous request, and notify the main thread when the request is done. This will prevent the block. The question then is bench marking your code and seeing which has more overhead, a threaded synchronous request or an asynchronous request.

The problem with asynchronous requests is you need to either setup a smart notification or delegate system as you can have multiple requests for multiple resources happening at any given time. The other problem with them is if I have a class, say a singleton which is handling all of my data, I can't use asynchronous requests in a getter method. Meaning the following won't go:

 - (NSArray *)users {
     if(users == nil)
        users = do_async_request // NO GOOD

     return users;
 }

whereas the following:

 - (NSArray *)users {
    if(users == nil)
      users == do_sync_request // OK.

    return users;
 }

You also might have priority. What I mean by priority is if you look at Apple's Mail application on the iPhone, you'll notice they first suck down your entire POP/IMAP tree before making a second request to retrieve the first 2 lines (the default) of your message.

I suppose my question to you experts is this. When are you using asynchronous, synchronous, threads -- and when are you using either async/sync in a thread? What kind of delegation system do you have setup to know what to do when a async request completes? Are you prioritizing your async requests?

There's a gamut of solutions to this all too common problem. It's simple to hack something out. The problem is, I don't want to hack and I want to have something that's simple and easy to maintain.

+1  A: 

I personally look at what is being done, I will ususally use an asyc request to ensure that the UI doesn't block, however, I MAY during the course of that request disable the UI of my application.

A prime example of this is in an application that I built with a "search" button. Once the search was triggered as an async request I would disable the button until the response came back, effectivly limiting the ability for the user to spawn a second asyc request.

Doing this, at least I can prevent the need for priorites, granted this only works if you can in an easy to do way, limit your users to one action at a time.

Mitchel Sellers
+3  A: 

I don't think that there's a "right" answer. It seems that you understand the compromises involved and you just need to make your design around those.

A few extra random points: sometimes your application forces a particular approach. For example, many of the convenience (i.e., synchronous) methods won't allow authentication. For me that meant that my decision was made.

For Yummy I ended up not using threads. I made all my network calls asynchronous and I used the default XML parser (which works using call backs). Since it's all event driven and each unit is small it allows the GUI to be pretty fluid without having the complexity of threading.

I use a state machine to figure out why I'm getting a particular response, and a queue so that I only need to have a single operation "in flight" at any given time. There's a distinct order to most requests so I have no need for a priority system.

The networking code is the most complex in my app and it took a long time to get working much less robust!

Stephen Darlington
Stephen, I think a state machine as well as a queue would help me a ton. I'm assuming you don't have an example anywhere laying around?
Coocoo4Cocoa
"State machine" was a rather grand way of phrasing it... it's a few booleans and a number of switch statements. Nothing terribly clever but I found drawing it out on a piece of paper beforehand was useful. The queue is just an NSMutableArray.
Stephen Darlington
+1  A: 

I'd recommend the asychronous way, no question. Then, load the information only when needed, and use a delegate system to give that information to the correct object.

You don't want to block the UI. Ever. And loading information asynchronously allows you better control over what's happening so you can throw up an error message if needed.

August
A: 

Why can't you use an asynchronous request like so:

- (NSArray *)users {
     if(users == nil && !didLaunchRequestAlready )
        users = do_async_request // Looks good to me
     return users;
 }

Asynchronous is absolutely the only option - the only real question is if you want to start using separate threads, or if you want to just use the asynch calls. Start there and look at managing threads if you really need to.

Kendall Helmstetter Gelner
Your return statement would execute before the async request has finished, so you wouldn't be getting the results, just nil.
Danny Tuppeny
@Danny: You'd probably want to setup a delegate method/notification which would signal that the request has finished, and populate a UITableView or whatever.
sebnow
@Danny: I was just echoing the code the questioner presented. In reality you want to return users because later on it would have been set, and when the async method finished you would do what @sebnow said, signal that the data was ready.
Kendall Helmstetter Gelner
+5  A: 

I'm not discounting asynchronous delegate calls, but I usually end up using a threaded worker class with synchronous requests. I find it's easier in the long run to have a well defined, threaded API, instead of filling up your controller with code managing the state between asynchronous methods. You could even make asynchronous in your worker thread, although usually it's easier to use the synchronous methods unless they don't support a feature you need to use. Of course, all of this depends on the circumstances, I can think of many situations where simply using the asynchronous methods would be the best route.

Definitely consider NSOperationQueue if you go this route; it greatly simplifies creating multiple worker threads, and it also supports priorities and dependancies between operations. Right now there are some problems with it on 10.5, but I haven't heard of any issues on the iPhone.

Marc Charbonneau
+3  A: 

An official response is that you should almost always go asynchronous and that synchronous is bad. I found ASIHTTPRequest makes asynchronous requests easy-peasy.

bbrown
+1  A: 

Just a thought: If you want to use the Unit Testing framework for the iPhone, you may want to have synchronous functionality, as that could make writing the tests easier.

However, some of your APIs may not work synchronously, so you need to turn them into sync tasks. Provided the code that performs the unit testing runs in its own thread, you can write a wrapper that will wait until the async task has finished.

To accomplish that, you'd use a semaphore: Your wrapper function starts the async operation and then uses the semaphore to block itself (i.e. puts itself to sleep). The callback that signals the end of the async event releases the semaphore, so that the sleeping wrapper thread can continue and return.

Thomas Tempelmann
HI Thomas, I am running into exactly this issue in my unit test after refactoring my code from synch calls to asynch calls. Could you explain in more detail about the semaphores? If you have an example, that would be awesome. Thanks.
Shiun
Here's a brief example: NSLock *theLock; int state;- (void) xy:(MyClass*)xy finished:(NSError *)error{ state = -1; [theLock unlock];}- (void) testBasics{ MyClass *c = [[MyClass new]]; theLock = [NSLock new]; [theLock lock]; state = 0; [c doWithDelegate:self]; [theLock lock]; [theLock unlock]; [theLock release]; [c release];}
Thomas Tempelmann