- Dataflow Programming / Concurrent Operations
- Operation Types / Adding Dependencies
- Cancelling Operations / Returning to Synchronous Behavior
Operation Types
You can write your own operation subclasses, but two are provided by default, one introduced with OS X 10.5 and the other with OS X 10.6. The first, NSInvocationOperation, uses an invocation to describe the operation-effectively turning a message-send into an operation. The second kind lets you construct an operation from a block. This is more convenient than writing a new class to implement a single short method; where code would go in the -main method of the subclass, you can just write it in a block instead.
Using block operations makes scheduling an NSOperation almost as simple as using libdispatch directly. This is all you need to do to schedule a block to run in an existing operation queue:
NSOperation *op = [NSBlockOperation operationWithBlock: ^(void) { /* code here */ } ]; [queue addOperation: op];
A convenience method in NSOperationQueue actually lets you do this in one step:
[queue addOperationWithBlock: ^(void) { /* code here */ } ];
The documentation says that this usage creates an operation, but I wouldn't be surprised if it actually passed the block directly to libdispatch.
But this approach doesn't give us anything that we can't do trivially with libdispatch, because we're not using the feature that makes operations so useful: dependencies.
Adding Dependencies
The real power of NSOperationQueue is that it frees you from having to run tasks explicitly. It's very common to have bits of work to do, with dependencies between those bits. You can manage these dependencies yourself, but it's much more convenient to let Cocoa do the work for you.
You can get a lot of benefit from operation queues with just one method: -addOperation: schedules an operation to run as soon as all of its dependencies have finished.
Operations are responsible for managing their own dependencies, and NSOperationQueue uses key-value observing to monitor them. If you haven't previously come across key-value observing (KVO), it's worth familiarizing yourself with this technique. KVO is a core part of the Foundation framework that mirrors key-value coding (KVC). KVC provides an abstract way of setting properties on objects. When you set a KVC property via the -setValue:forKey: method, it may set an instance variable directly, call a method, or call the -setValue:forUndefinedKey: method to provide a fallback. KVO allows you to watch KVC properties. Classes that use the KVC accessors only for setting and getting properties don't need modifying at all for KVO to work; others may need a small amount of work.
In this context, KVO allows the operation to watch the isFinished property on its dependencies. It then sets its isReady property, and the queue prepares to run it. All you need to do to ensure the correct ordering of your operations is add prerequisites as dependencies. The code can be as simple as this:
[operation addDependency: prerequisite1]; [operation addDependency: prerequisite2]; [operation addDependency: prerequisite3]; [queue addOperation: operation];
In this example, the queue waits until the three prerequisite operations are finished before it schedules the new operation to run. If the prerequisites are already in the queue, some of them may have finished by the time the new operation is ready, but that doesn't matter.