11

I'm currently using synchronous ASIHTTPRequest with GCD queues to download data from the Internet, then parse the response data with JSONKit. What do you think about this pattern. Thank you in advance.

Here is my code:

    dispatch_async(queue, ^(void) {

        // Request is ASIHTTPRequest.
        [request startSynchronous];

        // Parse JSON.
        NSArray *array = [[request responseData] objectFromJSONDataWithParseOptions:JKParseOptionLooseUnicode];

        // Callback on the main queue to update UI.
        dispatch_async(dispatch_get_main_queue(), ^(void) {
            callbackBlock(array);
        });
    });

EDIT: The reason I use ASIHTTPRequest is that I need to modify the request header for OAuth and use POST method to upload images.

nonamelive
  • 6,510
  • 8
  • 40
  • 47
  • I'm not a GCD expert by any means, but I don't see why the inner block needs to be executed asynchronously since it is running in the outer block which is already executing asynchronously. – titaniumdecoy Jul 06 '11 at 04:47
  • @Jason, it needs to be done this way to run back on the main thread. – Steve Jul 06 '11 at 05:31
  • My understanding is that dispatch_sync wil execute your block on a different thread which you specify (such as the main thread); the difference is that it will block the current the current thread until it is finished. The GCD documentation seems to indicate that it may be more efficient than dispatch_async. – titaniumdecoy Jul 06 '11 at 05:38
  • I _suspect_ the main efficiency gain is the possibility of running the block on the same thread. A `dispatch_sync` to the main queue can’t do that, but will need to set up synchronization that a `dispatch_async` doesn’t. – Jens Ayton Jul 06 '11 at 07:04
  • This is a pattern that I use as well. I would be interested to hear any reasons why I should/shouldn't continue using it. :) – Aidan Steele Jul 06 '11 at 07:43
  • @nonamelive Would be great if you could list out what you see as the advantages and disadvantages of this pattern! – JosephH Jul 06 '11 at 10:30
  • @JosephH Well, first, this pattern is very straitforward, very easy to maintain -- there is only one method to control the whole download-parse-callback process. Second, all operations are in background, thus the main thread won't be blocked by neither the download operation nor the JSON parsing operation. Third, using ASIHTTPRequest makes POST method request and OAuth support effortlessly. – nonamelive Jul 06 '11 at 14:00

4 Answers4

12

So you replaced this

- (void)doDownload {
    NSURL *url = [NSURL URLWithString:@"http://foobar.com"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    connection = [[NSURLConnection alloc] initWithRequest:aURLRequest delegate:self];
    receivedData = [[NSMutableData data] retain];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [_receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSArray *array = [_receivedData objectFromJSONDataWithParseOptions:JKParseOptionLooseUnicode];
    callbackBlock(array);       
}

with this -

- (void)doDownload {
    NSURL *url = [NSURL URLWithString:@"http://foobar.com"];
    ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^(void) {
        [request startSynchronous];
        NSArray *array = [[request responseData] objectFromJSONDataWithParseOptions:JKParseOptionLooseUnicode];

        // Callback on the main queue to update UI.
        dispatch_async(dispatch_get_main_queue(), ^(void) {
            callbackBlock(array);
        });
    });
}

and 10,000+ lines of code from ASIHTTPRequest.

What has it got you?

NSURLConnection is fully asynchronous, uses GCD, caches, automatic zip/unzip, etc, etc..

For that reason, and going solely on the (possibly incomplete) information you provided, i'd say that it was a really awful piece of code.

Of course, context is everything - and you may have a really, really, really good reason for reimplementing the already existing functionality of Library code provided by Apple.

hooleyhoop
  • 9,128
  • 5
  • 37
  • 58
  • 6
    Your answer is quite valid, but perhaps a little heavy on the sarcasm… – Rob Keniger Jul 06 '11 at 10:23
  • I make ASIHTTPRequest to be under 6,000 lines (based on wc -l *.m) - less if you're not using some of the optional features (though this doesn't invalidate your argument). A key difference is that your NSURLConnection code runs the json parsing on the main thread, whereas the code in the question runs it on a different thread I believe - useful to avoid blocking the main thread if the JSON is large. – JosephH Jul 06 '11 at 10:29
  • @rob - honestly, no sarcasm intended. I believe there are cases when ASIHTTPRequest is really useful and provides missing functionality. However, (it seems like) there are so many questions on SO about "using GCD to download on a background thread" or "using async NSOperation and NSURLConnection" and so on.. Most of the 'solutions' are overkill or even detrimental to the basic, standard, simple use case of NSURLConnection. – hooleyhoop Jul 06 '11 at 10:34
  • 1
    @JosephH 6,000 lines of (possibly) unnecessary code is still a lot. I admit, i counted some headers as well. I didn't do this to mislead, it was just a lazy mistake. I don't believe that the exact number of lines is critical to the point. Also it would be trivial to parse the json on a different thread (move the code down 1 line into the block and run the block on a background thread) without resorting to an external framework. – hooleyhoop Jul 06 '11 at 10:42
  • @fakeAccount22: Sometimes asynchronous operations are a pain, especially if you have dozens of web requests that need to be made with different corresponding logic. Synchronous connections that can be made to operate in the background trivially (GCD) simplify code flow considerably. `NSURLConnection`'s support for synchronous connections is lacking, but `ASIHTTPRequest` handles it nicely. – Aidan Steele Jul 06 '11 at 22:20
  • @MrAlien: Ah, i see what you are saying, i see how it could be convenient. However don't think that putting several synchronous operations in background queue gets you anything close to the behaviour of an asynchronous URL connection. eg, On an iPhone many downloads added to a background queue might download serially because GCD is using one thread per processor, on a mac pro that might be 24 at a time - in both cases completely without regard to the optimum of the device and connection. – hooleyhoop Jul 07 '11 at 10:12
  • One detrimental thing that putting `n` synchronous operations in the background does is it consumes threads that count toward the GCD thread pool limit (anecdotally observed to be 64 at the time of this writing) and the per-process thread limit. Using synchronous API for network IO on any modern OS is either an oversight (i.e. you didn't know any better) or it's laziness. – ipmcc Aug 02 '13 at 12:24
2

At WWDC2010, for network programming, Apple recommends to use asynchronous programming with RunLoop.

  • WWDC 2010 Session 207 - Network Apps for iPhone OS, Part 1
  • WWDC 2010 Session 208 - Network Apps for iPhone OS, Part 2

NSURLConnection asynchronous request is one of the most efficient way. But if you want to use ASIHTTPRequest, how about it? (ASIHTTPRequest asynchronous request is implemented using NSOperationQueue, it is not same as NSURLConnection asynchronous.)

ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        NSData *responseData = [request responseData];
        /* Parse JSON, ... */
        dispatch_async(dispatch_get_main_queue(), ^{
            callbackBlock(array);
        });
    });
}];
[request setFailedBlock:^{
    NSError *error = [request error];
    /* error handling */
}];
[request startAsynchronous];
Kazuki Sakamoto
  • 13,929
  • 2
  • 34
  • 96
  • So even in this way, the download process is still not RunLoop based. Is there a big difference between your code and my code. The only thing I can see is that you use NSOperationQueue and I use GCD for the download, respectively. Well, in my opinion, I'm kind prefer your implementation now, but I can't say the reason. :) – nonamelive Jul 07 '11 at 01:37
  • At least, my code does not block directly the dispatch queue by synchronous network request (it blocks indirectly a dispatch queue via NSOperationQueue :-) ). – Kazuki Sakamoto Jul 07 '11 at 01:46
  • Yes, I kinda understand that. Seems the best way is to wrap an NSURLConnection class with block support -- Runloop based download & GCD parsing. What do you think about this, but I need to add a lot of code for my needs of OAuth & POST method implementation. – nonamelive Jul 07 '11 at 01:54
  • I wrote about [NSURLConnection with GCD pattern](http://stackoverflow.com/questions/5037545/nsurlconnection-and-grand-central-dispatch/5151402#5151402), but actually as you said, you need to implement for OAuth and so on. How about [OAuthCore](https://bitbucket.org/atebits/oauthcore)? – Kazuki Sakamoto Jul 07 '11 at 02:03
0

That code looks ok for a single network connection, but if you are using ASIHTTPRequest you will likely be a mobile application. For multiple concurrent downloads I would implement a queue (see "Using a queue" in the ASIHTTPRequest docs) where you can specify the amount of maximum simultaneous connections (say use 2 on GPRS and 8 on wifi) or throttle the bandwidth. Then, in the finish selector use GDC or something else to run the processing of the data out of the main UI thread.

In essence, using blocks with ASIHTTPRequest for the simple case only brings you a different syntax with regards to NSURLConnection, as fakeAccount22 mentioned. And NSURLConnection also has synchronous methods, so you can avoid an external dependency (and potential source of bugs/problems) and use that in blocks.

Grzegorz Adam Hankiewicz
  • 7,349
  • 1
  • 36
  • 78
0

Best way to do it is to use gcd when getting the callback and not when initiating the request. Then you could parse on background thread and notifiy on main thread. Good luck!

hfossli
  • 22,616
  • 10
  • 116
  • 130