In life, there’s always work to be done. Every day brings with it a steady stream of tasks and chores to fill the working hours of our existence.

Yet, no matter how burdened one’s personal ToDo list becomes, it pales in comparison to the workload of an iOS app, of which millions of computations are expected, all while managing to draw a frame every 16 milliseconds.

Productivity is, as in life as it is in programming, a matter of scheduling and prioritizing and multi-tasking work in order to keep up appearances.

The secret to making apps snappy is to offload as much unnecessary work to the background as possible, and in this respect, the modern Cocoa developer has two options: Grand Central Dispatch and NSOperation . This article will primarily focus on the latter, though it’s important to note that the two are quite complementary (more on that later).

NSOperation represents a single unit of work. It’s an abstract class that offers a useful, thread-safe structure for modeling state, priority, dependencies, and management.

For situations where it doesn’t make sense to build out a custom NSOperation subclass, Foundation provides the concrete implementations NSBlock Operation and NSInvocation Operation .

Examples of tasks that lend themselves well to NSOperation include network requests, image resizing, text processing, or any other repeatable, structured, long-running task that produces associated state or data.

But simply wrapping computation into an object doesn’t do much without a little oversight. That’s where NSOperation Queue comes in:

NSOperationQueue

NSOperation Queue regulates the concurrent execution of operations. It acts as a priority queue, such that operations are executed in a roughly First-In-First-Out manner, with higher-priority ( NSOperation.queue Priority ) ones getting to jump ahead of lower-priority ones. NSOperation Queue can also limit the maximum number of concurrent operations to be executed at any given moment, using the max Concurrent Operation Count property.

NSOperationQueue itself is backed by a Grand Central Dispatch queue, though that’s a private implementation detail.

To kick off an NSOperation , either call start , or add it to an NSOperation Queue , to have it start once it reaches the front of the queue. Since so much of the benefit of NSOperation is derived from NSOperation Queue , it’s almost always preferable to add an operation to a queue rather than invoke start directly.

State

NSOperation encodes a rather elegant state machine to describe the execution of an operation:

ready → executing → finished

In lieu of an explicit state property, state is determined implicitly by KVO notifications on those keypaths. When an operation is ready to be executed, it sends a KVO notification for the ready keypath, whose corresponding property would then return true .

Each property must be mutually exclusive from one another in order to encode a consistent state:

ready : Returns true to indicate that the operation is ready to execute, or false if there are still unfinished initialization steps on which it is dependent.

: Returns to indicate that the operation is ready to execute, or if there are still unfinished initialization steps on which it is dependent. executing : Returns true if the operation is currently working on its task, or false otherwise.

: Returns if the operation is currently working on its task, or otherwise. finished Returns true if the operation’s task finished execution successfully, or if the operation was cancelled. An NSOperation Queue does not dequeue an operation until finished changes to true , so it is critical to implement this correctly in subclasses to avoid deadlock.

Cancellation

It is often useful to cancel operations early to prevent needless work from being performed, whether due to a failure in a dependent operation or explicit cancellation by the user.

Similar to execution state, NSOperation communicates cancellation through KVO on the cancelled keypath. When an operation is cancelled, it should clean up any internal details and arrive in an appropriate final state as quickly as possible. Specifically, the values for both cancelled and finished need to become true , and executing needs to become false .

One thing to watch out for are the spelling peculiarities of the word “cancel”. Although spelling varies across dialects, when it comes to NSOperation :

cancel : use one L for the function (verb)

: use one L for the function (verb) cancelled : use two L’s for the property (adjective)

Priority

All operations may not be equally important. Setting the queue Priority property will promote or defer an operation in an NSOperation Queue according to the following rankings:

NSOperationQueuePriority

Swift Objective-C public enum NSOperation Queue Priority : Int { case Very Low case Low case Normal case High case Very High } typedef NS_ENUM ( NSInteger , NSOperation Queue Priority ) { NSOperation Queue Priority Very Low = - 8L , NSOperation Queue Priority Low = - 4L , NSOperation Queue Priority Normal = 0 , NSOperation Queue Priority High = 4 , NSOperation Queue Priority Very High = 8 };

Quality of Service

Quality of Service is a new concept in iOS 8 & OS X Yosemite that creates consistent, high-level semantics for scheduling system resources. APIs were introduced for both XPC and NSOperation that use this abstraction.

For NSOperation , the thread Priority property has been deprecated in favor of this new quality Of Service property. (And good riddance— thread Priority was too unwieldy to be anything but a liability to most developers.)

Service levels establish the system-wide priority of an operation in terms of how much CPU, network, and disk resources are allocated. A higher quality of service means that more resources will be provided to perform an operation’s work more quickly.

QoS appears to use the XNU kernel task policy feature introduced in OS X Mavericks under the hood.

The following enumerated values are used to denote the nature and urgency of an operation. Applications are encouraged to select the most appropriate value for operations in order to ensure a great user experience:

NSQualityOfService

Swift Objective-C @available ( i OS 8.0 , OSX 10.10 , * ) public enum NSQuality Of Service : Int { case User Interactive case User Initiated case Utility case Background case Default } typedef NS_ENUM ( NSInteger , NSQuality Of Service ) { NSQuality Of Service User Interactive = 0x21 , NSQuality Of Service User Initiated = 0x19 , NSQuality Of Service Utility = 0x11 , NSQuality Of Service Background = 0x09 , NSQuality Of Service Default = - 1 } NS_ENUM_AVAILABLE ( 10 _10 , 8 _0 );

.User Interactive :UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen.

:UserInteractive QoS is used for work directly involved in providing an interactive UI such as processing events or drawing to the screen. .User Initiated : UserInitiated QoS is used for performing work that has been explicitly requested by the user and for which results must be immediately presented in order to allow for further user interaction. For example, loading an email after a user has selected it in a message list.

: UserInitiated QoS is used for performing work that has been explicitly requested by the user and for which results must be immediately presented in order to allow for further user interaction. For example, loading an email after a user has selected it in a message list. .Utility : Utility QoS is used for performing work which the user is unlikely to be immediately waiting for the results. This work may have been requested by the user or initiated automatically, does not prevent the user from further interaction, often operates at user-visible timescales and may have its progress indicated to the user by a non-modal progress indicator. This work will run in an energy-efficient manner, in deference to higher QoS work when resources are constrained. For example, periodic content updates or bulk file operations such as media import.

: Utility QoS is used for performing work which the user is unlikely to be immediately waiting for the results. This work may have been requested by the user or initiated automatically, does not prevent the user from further interaction, often operates at user-visible timescales and may have its progress indicated to the user by a non-modal progress indicator. This work will run in an energy-efficient manner, in deference to higher QoS work when resources are constrained. For example, periodic content updates or bulk file operations such as media import. .Background : Background QoS is used for work that is not user initiated or visible. In general, a user is unaware that this work is even happening and it will run in the most efficient manner while giving the most deference to higher QoS work. For example, pre-fetching content, search indexing, backups, and syncing of data with external systems.

: Background QoS is used for work that is not user initiated or visible. In general, a user is unaware that this work is even happening and it will run in the most efficient manner while giving the most deference to higher QoS work. For example, pre-fetching content, search indexing, backups, and syncing of data with external systems. .Default : Default QoS indicates the absence of QoS information. Whenever possible QoS information will be inferred from other sources. If such inference is not possible, a QoS between UserInitiated and Utility will be used.

Swift Objective-C let background Operation = NSOperation () background Operation . queue Priority = . Low background Operation . quality Of Service = . Background let operation Queue = NSOperation Queue . main Queue () operation Queue . add Operation ( background Operation ) NSOperation * background Operation = [[ NSOperation alloc ] init ]; background Operation . queue Priority = NSOperation Queue Priority Low ; background Operation . quality Of Service = NSOperation Quality Of Service Background ; [[ NSOperation Queue main Queue ] add Operation : background Operation ];

Asynchronous Operations

Another change in iOS 8 / OS X Yosemite is the deprecation of the concurrent property in favor of the new asynchronous property.

Originally, the concurrent property was used to distinguish between operations that performed all of their work in a single main method, and those that managed their own state while executing asynchronously. This property was also used to determine whether NSOperation Queue would execute a method in a separate thread. After NSOperation Queue was changed to run on an internal dispatch queue rather than manage threads directly, this aspect of the property was ignored. The new asynchronous property clears away the semantic cobwebs of concurrent , and is now the sole determination of whether an NSOperation should execute synchronously in main , or asynchronously.

Dependencies

Depending on the complexity of an application, it may make sense to divide up large tasks into a series of composable sub-tasks. This can be done with NSOperation dependencies.

For example, to describe the process of downloading and resizing an image from a server, one might divide up networking into one operation, and resizing into another (perhaps to reuse the networking operation to download other resources, or also use the resizing operation for images already cached in memory). However, since an image can’t be resized until it’s downloaded, then the networking operation is a dependency of the resizing operation, and must be finished before the resizing operation can be started.

Expressed in code:

Swift Objective-C let networking Operation : NSOperation = ... let resizing Operation : NSOperation = ... resizing Operation . add Dependency ( networking Operation ) let operation Queue = NSOperation Queue . main Queue () operation Queue . add Operations ([ networking Operation , resizing Operation ], wait Until Finished : false ) NSOperation * networking Operation = ... NSOperation * resizing Operation = ... [ resizing Operation add Dependency : networking Operation ]; NSOperation Queue * operation Queue = [ NSOperation Queue main Queue ]; [ operation Queue add Operation : networking Operation ]; [ operation Queue add Operation : resizing Operation ];

An operation will not be started until all of its dependencies return true for finished .

Make sure not to accidentally create a dependency cycle, such that A depends on B, and B depends on A, for example. This will create deadlock and sadness.

completion Block

When an NSOperation finishes, it will execute its completion Block exactly once. This provides a really nice way to customize the behavior of an operation when used in a model or view controller.

Swift Objective-C let operation = NSOperation () operation . completion Block = { print ( "Completed" ) } NSOperation Queue . main Queue () . add Operation ( operation ) NSOperation * operation = ...; operation . completion Block = ^ { NSLog ( "Completed" ); }; [[ NSOperation Queue main Queue ] add Operation : operation ];

For example, you could set a completion block on a network operation to do something with the response data from the server once it’s finished loading.

NSOperation remains an essential tool in an iOS or OS X developer’s bag of tricks. Whereas GCD is ideal for in-line asynchronous processing, NSOperation provides a more comprehensive, object-oriented model of computation for encapsulating all of the data around structured, repeatable tasks in an application.

Developers should use the highest level of abstraction possible for any given problem, and for scheduling consistent, repeated work, that abstraction is NSOperation . Other times, it makes more sense to sprinkle in some GCD (including within an NSOperation subclass implementation).

When to Use Grand Central Dispatch

Dispatch queues, groups, semaphores, sources, and barriers comprise an essential set of concurrency primitives, on top of which all of the system frameworks are built.

For one-off computation, or simply speeding up an existing method, it will often be more convenient to use a lightweight GCD dispatch than employ NSOperation .

When to Use NSOperation

NSOperation can be scheduled with a set of dependencies at a particular queue priority and quality of service. Unlike a block scheduled on a GCD queue, an NSOperation can be cancelled and have its operational state queried. And by subclassing, NSOperation can associate the result of its work on itself for future reference.

Just remember: NSOperation and Grand Central Dispatch are not mutually exclusive. Creative and effective use of both are key to developing robust and performant iOS or OS X applications.