I recently attended several interesting technical sessions at the CocoaConf in Raleigh. In this post I'd like to give an overview of one particular session regarding iOS Concurrency, presented by Jonathan Blocksom of Big Nerd Ranch. (http://www.bignerdranch.com/instructors/blocksom_jonathan)

The focus was on creating separate threads of execution in an iOS application. While early versions of iOS only provided one means of creating threads, NSThread, in iOS 3.2 Apple added additional ways to create and manage threads - Grand Central Dispatch (GCD) and NSOperationQueue. Jonathan's session touched on the advantages/disadvantages of the two newer approaches.

Jonathan's executive summary stated that both Grand Central Dispatch (GCD) and NSOperationQueue offer the following advantages over NSThread:

  • Less dependence on locks
  • Less need for semaphores
  • Less likely to introduce race conditions
  • Less impact on responsiveness of UI
  • Better scalability because they provide a management layer to handle thread pooling

Grand Central Dispatch

GCD offers some distinct advantages over creating a thread through NSThread. For one, GCD creates a thread pool based on the number of available processors. In other words, if we create 10 simultaneous threads in our application, GCD can determine, based on available processors, the number of concurrent threads of execution that can take place at any given time. This essentially means GCD handles the scheduling of threads across multiple processing cores. We don't have such a level of control with NSThread. With NSThread code executes immediately, which may cause sluggishness in our application, particularly when it needs to respond to touch events.

The sample fragment below illustrates a GCD call to invoke an asynchronous method cacheImages.

dispatch_async<span class="br0">(</span>dispatch_get_global_queue<span class="br0">(</span>DISPATCH_QUEUE_PRIORITY_BACKGROUND,<span class="nu0">0</span><span class="br0">)</span>, <span class="sy0">^</span><span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span> <span class="br0">{</span>
 <span class="br0">[</span>self cacheImages<span class="br0">]</span>;
<span class="br0">}</span><span class="br0">)</span>;
 
<span class="co2">// Pre-load all image thumbnails</span>
<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span> cacheImages
<span class="br0">{</span>
 <span class="kw1">for</span><span class="br0">(</span>MyImage <span class="sy0">*</span>imageEntry <span class="kw1">in</span> imageArray<span class="br0">)</span>
 <span class="br0">{</span>
 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span class="kw5">NSString</span></a> <span class="sy0">*</span>urlText <span class="sy0">=</span> <span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span class="kw5">NSString</span></a> <span class="sy0">*</span><span class="br0">)</span><span class="br0">[</span>imageEntry url<span class="br0">]</span>;
 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSURL_Class/"><span class="kw5">NSURL</span></a> <span class="sy0">*</span>url <span class="sy0">=</span> <span class="br0">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSURL_Class/"><span class="kw5">NSURL</span></a> URLWithString<span class="sy0">:</span>urlText<span class="br0">]</span>;
 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/"><span class="kw5">NSData</span></a> <span class="sy0">*</span>imageData <span class="sy0">=</span> <span class="br0">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/"><span class="kw5">NSData</span></a> dataWithContentsOfURL<span class="sy0">:</span>url<span class="br0">]</span>;
 UIImage <span class="sy0">*</span>image <span class="sy0">=</span> <span class="br0">[</span>UIImage imageWithData<span class="sy0">:</span>imageData<span class="br0">]</span>;
 <span class="br0">[</span>dictImages setValue<span class="sy0">:</span>image forKey<span class="sy0">:</span>urlText<span class="br0">]</span>;
 <span class="br0">}</span>
<span class="br0">}</span>

In this particular example we're creating a background task to pre-load images used in a view. If you look at Apple's documentation for GCD you'll find there are various options for controlling task priority, as well as whether the thread gets dispatched on the application's main queue (important for processing UI events) or an alternate queue. Our example code dispatches the thread on the global queue, since the cacheImages method doesn't need to interact with any application UI components.

Jonathan summarized the following GCD queue options:

  • global_queue – returns shared concurrent queue of the requested priority level
  • background_queue – returns lower-priority (i.e. background) queue
  • main_queue – main processing queue (highest priority)
  • queue_create – creates a custom queue which, unlike the shared queues above, allows complete control over which operations are dispatched to this queue. You can create a custom queue as follows:

dispatch_queue_t queue = dispatch_queue_create("my.queue.com", NULL);

As well as different ways to dispatch a task:

  • dispatch_async – submits block for execution and returns
  • dispatch_sync – submits block for execution and waits
  • dispatch_after – executes block at specified time
  • dispatch_apply – submits block for multiple executions
  • dispatch_once – submits block for single execution

Please refer to Apple's developer documentation for a complete description of each option.

NSOperationQueue

Another way to use threading in your application is to create an NSOperationQueue and add one or more NSOperation objects to the queue. While NSOperationQueue existed prior to iOS 4, we discussed that previous implementations were problematic (i.e. random lockups, failures) because the underlying implementation was based on threads. Beginning with iOS 4 NSOperationQueue is implemented on top of GCD, which provides greater stability.

You can think of NSOperationQueue as an abstract layer for GCD. It provides additional control over GCD by allowing you to specify the width of the queue (i.e. the number of concurrent operations), queue priority, set dependencies between operations, and so forth. It also provides intrinsic block execution capabilities by using the addOperationWithBlock method to add NSOperation's to the queue. The following code fragment demonstrates adding an operation (myOperation, an instance of NSOperation) to the queue:

<span class="kw1">@interface</span> FetchImageOperation <span class="sy0">:</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSOperation_Class/"><span class="kw5">NSOperation</span></a> <span class="br0">{</span>
 <span class="kw1">@property</span> <span class="br0">(</span>nonatomic, strong<span class="br0">)</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span class="kw5">NSString</span></a> <span class="sy0">*</span>imageURL;
<span class="br0">}</span>
FetchImageOperation imgOperation <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>FetchImageOperation alloc<span class="br0">]</span> init<span class="br0">]</span>;
imgOperation.imageURL <span class="sy0">=</span> "http<span class="sy0">:</span><span class="co2">//myhost/image.png";</span>
 
<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSOperationQueue_Class/"><span class="kw5">NSOperationQueue</span></a> <span class="sy0">*</span>queue <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSOperationQueue_Class/"><span class="kw5">NSOperationQueue</span></a><span class="br0">]</span> alloc<span class="br0">]</span> init<span class="br0">]</span>;
<span class="br0">[</span>queue addOperation<span class="sy0">:</span>myOperation<span class="br0">]</span>;

Jonathan mentioned the following queue management options available with NSOperationQueue:

  • (NSArray *) operations - provides direct access to the array of NSOperation's in queue
  • cancelAllOperations - method to cancel outstanding operations
  • waitUntilAllOperationsFinished - method to suspend current thread of execution until queue is complete
  • suspend/resume - methods that affect current processing mode of queue

He additionally touched on the following attributes and methods available for each NSOperation:

  • start
  • cancel
  • isReady
  • isExecuting
  • isFinished
  • isCancelled

Please note in the example above we're executing the NSOperation's alloc and init on the current thread. Keep in mind that background threads only come into the picture once an NSOperation starts, so any actions performed in NSOperation init directly affect the current thread.

Jonathan also reiterated the importance of ensuring any UI-related operations take place on the main thread (as required by UIKit). He said you can accomplish this by either performing the entire operation on the main queue, or by performing a specific operation on the main thread such as this:

dispatch_async<span class="br0">(</span>dispatch_get_background_queue<span class="br0">(</span><span class="br0">)</span>, <span class="sy0">^</span><span class="br0">{</span>
 <span class="br0">[</span>self refreshImageCache<span class="br0">]</span>;
 <span class="br0">[</span>self calcTotalImageSize<span class="br0">]</span>;
 <span class="br0">[</span>self performSelectorOnMainThread<span class="sy0">:</span><span class="kw1">@selector</span><span class="br0">(</span>updateMyTextStatusField<span class="br0">)</span> withObject<span class="sy0">:</span><span class="kw2">nil</span> waitUntilDone<span class="sy0">:</span><span class="kw2">NO</span><span class="br0">]</span>;
<span class="br0">}</span><span class="br0">)</span>;

where performSelectorOnMainThread forces main thread execution of the application method updateMyTextStatusField, which updates a text element.

Again, please refer to Apple's developer documentation for a complete description of each of the above options as well as a detailed description of setting up an NSOperation subclass.

Summary

In conclusion we can summarize all three threading approaches as follows:

NSThread

Is a good option for quick tasks where you don't need sophisticated management of multiple threads. Less system overhead than GCD and NSOperationQueue, so performance can be slightly better. But, offers very little control (i.e. thread pooling, pause/resume and so forth). May also introduce unintended side effects related to deadlocks.

GCD

Wraps threading API into a more robust model, which allows ability to create and manage multiple queues. Is scalable, providing automatic creation of thread pools based on available processor cores. Offers less direct control. Threads start/resume based on GCD controlled scheduling.

NSOperationQueue

Is an abstract layer built on GCD. Provides additional abstraction and enhanced queue management capabilities, such as creating operation blocks, suspend/resume, cancel and so forth. Is a good option when you need more sophisticated control over threading than GCD provides.

The choice of threading approach is obviously a tradeoff between simplicity (NSThread) versus flexibility (NSOperationQueue). For all but the most timing-critical applications NSOperationQueue is often the best choice due to its advanced scheduling capabilities, higher level of abstraction and scalability.