Operations are a abstraction of threads built upon GCD that I’m not sure many people are aware of. Here I’ll talk about creating subclasses of Operation, but just be aware that there is a default subclass of Operation called a BlockOperation that is useful for doing small units of work if you need it.
Operation is an abstract class that represents a unit of work to be completed on a queue. They can be initialised, their work performed on a queue, and notify through KVO when its state changes. There are a lot of cool things about Operations, one of them being that they are highly reusable in the sense that you can just create a new instance of your Operation subclass and pass in different values for each instance. You can add your Operations to an OperationQueue, and the queue will start your Operations itself.
To start, let’s create our own Operation subclass. The only requirement we need is to override the main() method from the Operation superclass.
Now we can create an instance of this Operation subclass, passing in the numbers we want to add. Then call .start() on it and in the next line print its result variable.
This code reads very synchronously, and in fact it is. If you put this in a Playground (or download the Playground at the bottom of this post), you’ll see it takes 5 seconds for the result to be printed. You definitely don’t want to be calling .start() on this operation from the main queue as that will totally lock up your UI while this executes.
That’s how to write a synchronous Operation subclass. Now let’s work on adding this to an OperationQueue. An OperationQueue handles scheduling and execution of the Operations passed to it. OperationQueue has a default initialiser, and then you can use the addOperation method to add your Operations to it. An OperationQueue has a few properties on it, two of them being OperationQueue objects – current and main. Current is the queue you’re on, and main is where you should do all UI updates. It also has a maxConcurrentOperationCount property, which if set to 1, makes the OperationQueue a serial queue.
OperationQueue also has methods to cancelAllOperations() and waitUntilAllOperationsAreFinished(). A queue’s QOS is determined by the Operations inside it, or you can set the qualityOfService variable to set the QOS of the entire queue – but this will be overwritten by the Operations in the queue. You can pause a queue with the isSuspended property – currently running Operations will continue, and you can continue to add more Operations, but the new ones will not execute until you set isSuspended to false.
Operations are run in the order that they’re added to the OperationQueue. An Operation leaves the queue when it is finished or cancelled. You cannot add the same instance of an Operation to any OperationQueue if it has already been added to an OperationQueue before. An Operation is a single-shot object – it executes its task once and can’t be used to execute again.
Let’s take a look at how you’d create a new OperationQueue.
Using the same SlowAddOperation class, we create two instances, create our OperationQueue, add our Operations to the queue and then call .waitUntilAllOperationsAreFinished(). This will synchronously wait for the OperationQueue to indicate that it’s complete before moving on to execute the operation.result and secondOperation.result lines.
That’s great and all, but how do we make this asynchronous? To do this, we’ll create a subclass of Operation called AsyncOperation. For every async operation we want, we then subclass AsyncOperation, call the asynchronous work in the main() function call, and in the completion block for that call, set the state property of AsyncOperation to .finished.
So now let’s rewrite our original SlowAddOperation to be a subclass of AsyncOperation instead:
and now we can use it as follows:
You’ll notice now that even though the fasterAsyncOperation gets added last, its completion is called first! The final thing I want to talk about here is the use of Dependencies. What happens if you want to use the result for slowAsyncOperation as a parameter in fasterAsyncOperation?
This is the problem that Dependencies solve! Now typically, doing this would mean having a function with a completion that returns a number, and then inside that completion we call the same function again but use the result of the completion as the new parameter. Something like this:
You can see here the nested function calls, the stairway of hell, the somewhat difficult to read code. We can make use of Dependencies with AsyncOperation to fix this and make the code read more sequentially.
So now we have code that reads sequentially and runs asynchronously. You probably won’t need to create Operations to do something as basic as above, but for asynchronous code that you use a lot, like applying a filter to an image, can be written using this Operation approach.