Question

Say I want to implement a pattern like so:

a = some array I download from the internet
b = manipulate a somehow (long operation)
c = first object of b

These would obviously need to be called synchronously, which is causing my problems in Objective C. I've read about NSOperationQueue and GCD, and I don't quite understand them, or which would be appropriate here. Can someone please suggest a solution? I know I can also use performSelector:@selector(sel)WaitUntilDone, but that doesn't seem efficient for larger operations.

Was it helpful?

Solution

So create a serial dispatch queue, dump all the work there (each in a block), and for the last block post a method back to yourself on the main queue, telling your control class that the work is done.

This is by far and away the best architecture for much such tasks.

OTHER TIPS

I'm glad your question is answered. A couple of additional observations:

  1. A minor refinement in your terminology, but I'm presuming you want to run these tasks asynchronously (i.e. don't block the main queue and freeze the user interface), but you want these to operate in a serial manner (i.e., where each will wait for the prior step to complete before starting the next task).

  2. The easiest approach, before I dive into serial queues, is to just do these three in a single dispatched task:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self doDownloadSynchronously];
        [self manipulateResultOfDownload];
        [self doSomethingWithFirstObject];
    });
    

    As you can see, since this is all a single task running these three steps after each other, you can really dispatch this to any background queue (in the above, it's to one of the global background queues), but because you're doing it all within a given dispatched block, these three steps will sequentially, one after the next.

    Note, while it's generally inadvisable to create synchronous network requests, as long as you're doing this on a background queue, it's less problematic (though you might want to create an operation-based network request as discussed below in point #5 if you want to enjoy the ability to cancel an in-progress network request).

  3. If you really need to dispatch these three tasks separately, then just create your own private serial queue. By default, when you create your own custom dispatch queue, it is a serial queue, e.g.:

    dispatch_queue_t queue = dispatch_queue_create("com.company.app.queuename", 0);
    

    You can then schedule these three tasks:

    dispatch_async(queue, ^{
        [self doDownloadSynchronously];
    });
    
    dispatch_async(queue, ^{
        [self manipulateResultOfDownload];
    });
    
    dispatch_async(queue, ^{
        [self doSomethingWithFirstObject];
    });
    
  4. The operation queue approach is just as easy, but by default, operation queues are concurrent, so if we want it to be a serial queue, we have to specify that there are no concurrent operations (i.e. the max concurrent operation count is 1):

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    
    [queue addOperationWithBlock:^{
        [self doDownloadSynchronously];
    }];
    
    [queue addOperationWithBlock:^{
        [self manipulateResultOfDownload];
    }];
    
    [queue addOperationWithBlock:^{
        [self doSomethingWithFirstObject];
    }];
    

    This begs the question of why you might use the operation queue approach over the GCD approach. The main reason is that if you need the ability to cancel operations (e.g. you might want to stop the operations if the user dismisses the view controller that initiated the asynchronous operation), operation queues offer the ability to cancel operations, whereas it's far more cumbersome to do so with GCD tasks.

  5. The only tricky/subtle issue here, in my opinion, is how do you want to do that network operation synchronously. You can use NSURLConnection class method sendSynchronousRequest or just grabbing the NSData from the server using dataWithContentsOfURL. There are limitations to using these sorts of synchronous network requests (e.g., you can't cancel the request once it starts), so many of us would use an NSOperation based network request.

    Doing that properly is probably beyond the scope of your question, so I might suggest that you might consider using AFNetworking to create an operation-based network request which you could integrate in solution #4 above, eliminating much of the programming needed if you did your own NSOperation-based network operation.

  6. The main thing to remember is that when you're running this sort of code on a background queue, when you need to do UI updates (or update the model), these must take place back on the main queue, not the background queue. Thus if doing a GCD implementation, you'd do:

    dispatch_async(queue, ^{
        [self doSomethingWithFirstObject];
    
        dispatch_async(dispatch_get_main_queue(),^{
            // update your UI here
        });
    });
    

    The equivalent NSOperationQueue rendition would be:

    [queue addOperationWithBlock:^{
        [self doSomethingWithFirstObject];
    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // update your UI here
        }];
    }];
    

The essential primer on these issues is the Concurrency Programming Guide.

There are a ton of great WWDC videos on the topic including WWDC 2012 videos Asynchronous Design Patterns with Blocks, GCD, and XPC and Building Concurrent User Interfaces on iOS and WWDC 2011 videos Blocks and Grand Central Dispatch in Practice and Mastering Grand Central Dispatch

I would take a look at reactive cocoa (https://github.com/ReactiveCocoa/ReactiveCocoa), which is a great way to chain operations together without blocking. You can read more about it here at NSHipster: http://nshipster.com/reactivecocoa/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top