Hello and welcome to another Lextech iOS article. Today we are going to go over the theory behind NSOperation, NSOperationQueue and how to do background processing within your iOS Applications.
Performing lengthy tasks within your applications is a common thing. You might be downloading images from a server, performing a large sort, parsing big chunks of data or doing a processor-intensive calculation.
All of these tasks would most definitely have to be performed on a thread other than the main (UI) thread. If not, you could lock up your main thread for an extended period of time and have the OS close your application!
Fortunately for you, there are many alternatives within iOS. There’s the classic NSThread, which is quite low level and cumbersome to use (it was the only alternative back in the early iPhone SDK days!), there’s Grand Central Dispatch (with Blocks at its core) and there’s NSOperation.
There seems to be quite a big fuzz regarding Blocks and Grand Central Dispatch lately. They are certainly powerful and quite a lot of fun. Apple also seems to consider them important as many of their APIs are starting to use blocks for common tasks (array sorting, animations, presenting controllers, etc).
But using Grand Central Dispatch comes at a price. It’s a lower level API than NSOperation (it would be between NSThread and NSOperation to be precise), it’s a C API (meaning you can’t subclass blocks or work in an object oriented environment) and it has more rules and details to learn as opposed to NSOperation.
There are times when blocks can come in handy, and believe me they do! As a matter of fact you can even mix NSOperation with Blocks. For trivial and common tasks, however, it can be quite overkill having to work out all the details of Blocks and GCD.
Enter NSOperation and NSOperation Queue!
A fully object oriented way for you to perform tasks in the background without having to worry about the intricacies of block syntax or GCD queues.
To use NSOperation, you create your own operations and add them to a queue so they are executed. Let’s take a look at their theory a bit:
This is the official description of NSOperation from the Apple Docs:
“The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task.”
So, each NSOperation you create (and you’ll see what types there are soon) needs to be focused on a single task.
To get started with your own operations, you must create a subclass of NSOperation and override some methods. The methods you override will depend on whether you are doing a concurrent or non-concurrent operation.
If you want to do a concurrent operation then you must override these methods:
If you are doing non-concurrent operations then all you must override is the following method:
Since your subclass of NSOperation also inherits from NSObject, you can go ahead and add your own properties, methods and any other code necessary for your operation to work.
You can always cancel the execution of an operation using the cancel method.
Now, because NSOperation is multicore aware, you need to make sure your custom properties and methods are thread safe. Otherwise you might end up with corrupt data or locking problems.
Having seen what an NSOperation is let’s look at some more things you can do with them. First up is dependencies.
When working with background tasks you can never be sure in what order they’ll execute or when they’ll be done, this is true not just with operations but with threads and blocks as well. The problem with this is that some tasks might depend on a previous task being done.
Luckily, NSOperation lets you add and remove dependencies with very little work. Here are the methods you can call on an NSOperation instance in order to set dependencies:
Both of these methods receive an NSOperation parameter to depend on. Basically what you are saying when you add a dependency is: “I depend on the following operation to be done before I am executed”.
Besides dependencies you can also specify queue or thread priorities with the following methods:
This might come in handy when you want to make sure some tasks are executed faster than others. Do not, however, use these to specify dependencies. As you’ve already seen there are specific methods to specify that between operations.
Finally let’s discuss what happens when your operation is done. How can you know when your operation finished? How do you perform something back on the main thread?
There are a couple of ways to know when your operation has finished executing:
- A block or completion method at the end of your operation’s main method
For KVC/KVO, there are several properties you can observe:
You’ll want to use the isFinished to know when your operation is done, although the others might come in handy as well depending on your particular scenario.
A completion method or block within your operation is great if you know what you want to do with the results, but NSNotifications can also be used to have any one of your classes listen for when your operation finishes and perform any actions necessary.
In order to execute something on the main thread you can use blocks to get the main queue, or just call performSelectorOnMainThread:withObject:waitUntilDone:.
Before moving on to NSOperationQueue there are two more types of NSOperations you should look at. They are:
NSInvocation operation lets you call one of your own methods in a background thread without having to go through the whole process of subclassing NSOperation. You do this by calling the following method on an allocated NSInvocationOperation:
NSBlockOperation is similar to NSInvocationOperation but lets you use a block instead of a method. As opposed to NSInvocationOperation, the method used to call a block operation is the following class method:
It takes the block you want to execute in the background as a parameter and performs it without locking the main thread.
Both of these inherit from NSOperation so the same rules apply regarding dependencies and observing for changes in the execution of your operation.
NSOperationQueue is the class in charge of executing your NSOperations. After you add an operation to a queue it remains there until you explicitly cancel it or until execution is finished.
Queues are smart enough to execute operations according to priorities and dependencies set by you.
Just as with NSOperation, there are certain KVC/KVO properties you can observe for changes. They are:
Here’s an important consideration taken straight from Apple’s documentation:
“It is safe to use a single NSOperationQueue object from multiple threads without creating additional locks to synchronize access to that object.”
In order to create an NSOperationQueue you use the traditional alloc and init pattern (no designated initializer with parameters required) at which point you can begin adding operations to it or setting up your queue.
With a queue created and ready to be used, you can use the following methods to add operations to it:
After adding an operation to a queue, it will execute as soon as possible. If you want to control when your queue begins working then suspend it with setSuspended: or query its status with isSuspended.
Going back to adding operations, you can add a regular NSOperation object with the first 2 methods listed. The main difference between addOperation: and addOperations:waitUntilFinished: is that the latter will block the current thread until execution of the operations it contains are finished as long as you specify YES as the wait parameter.
Control is not returned to the caller if you do tell it to wait until finished. Be sure to take this into consideration and to check your parameters in case your thread is locking up or if you are getting unexpected results/delays.
The addOperationWithBlock: works exactly the same except it takes a block object, wraps it around an NSOperation and adds it to the queue. The block cannot take or return any parameters and you shouldn’t try to access the NSOperation object it’s contained in. Just add it to the queue and let it run.
This method is quite convenient if you just want to add a block operation without having to subclass NSOperation, if you are familiar with blocks or if you already have a block object that you want to run concurrently.
In order to cancel all operations, should you need to do so, you can call the cancelAllOperations at which point all operations in the queue will receive the cancel message. Any operations currently being executed will have to handle the cancel message on their own (i.e. checking for a cancel status during execution and handling accordingly)
Finally you can also set and check the max number of concurrent operations as well as setting and reading a queue’s name:
I hope this brief theory of NSOperation has helped make the API a bit clearer and easier for you to understand. Armed with this knowledge you should be better prepared to start coding and writing your own operations, or perhaps further understanding of the operations code you’ve been writing all along!