Parallel Programming with Operation and OperationQueue in Swift
You might have read my previous article on GCD, and want to explore the higher-level tools at your disposal in Swift.
You might want a deep-five into those higher-level tools. If you do, this is the article for you!
Welcome to the article!
Difficulty: Beginner | Easy | Normal | Challenging
This article has been developed using Xcode 11.5, and Swift 5.2.4
- This article is written in a Playground but I’m assuming you are comfortable with Initialization
- You might benefit from understanding the difference between parallelism and concurrency and GCD through those articles
- Later in the article a real example is used, that is Decoding JSON and the internal workings of this are kind of skipped over
Queue: A data structure in which elements are removed in the same order in which they were entered Operation: A class representing a single task. As an abstract class you should subclass it, or us
BlockOperation to perform the task. Operations are performed one time and cannot be executed again. OperationQueue: A priority queue that regulates the order of operations
Operation is an abstract class
Operation is usually subclassed in order to complete a specific task.
Each operation is in one of several possible states
Pending when an operation is added to an
OperationQueue is is in a pending state, waiting for preconditions. Ready once the preconditions are fulfulled, an operation is ready. If there is an open space on the
Operation will execure (move to the next state) Executing this state represents that the
Operation is running Finished this state represents that the
Operation has been completed, and should be removed from the
OperationQueue. Cancelled If an
Operation has been cancelled it should stop execution, discard all conditions and enter the finished state.
An operation can always be be cancelled — so long as it is not finished (or has already been cancelled, obvs.). The only way to remove an
Operation from the
OperationQueue is by entering the finished state.
Concurrency in operations
Operations can be Concurrent or Non-concurrent.
Concurrent operations can perform their work on different threads so we assume the responsibility for moving the
Operation between the Ready, Executing and Finished states.
Non-concurrent operations perform their work on the same thread and when the main method returns the operation is moved into the Finished state.
If you need to execute operations in a particular order (for example downloading JSON and then decoding it) this can be set up as a dependent operation. There can (of course) be more than one dependent operation, and an operation object with dependencies will not be executed until all of it’s dependent operations have finished executing.
Remember one thing though — If an operation is cancelled then it is still marked as finished — therefore this has some impact on error tracking and error handling.
Executing operations manually
It is possible to execute operations manually by calling the
start() function. Apple say that care must be taken to make sure that the operation is in the ready state before doing this - at risk of a runtime exception (and this can be checked with the
The minimal example!
Here we are going to create the minimal example for
OperationQueue. What is that example going to be? I'm goint to make a Hello, World! example that will make this as simple as possible.
At last! Let’s get started!
Instead of subclassing operation we can use
BlockOperation to manage the concurrent execution of blocks. Concurrent? That means operations running in the same time-space as each other. A diagram?
Using a custom operation
You should check if the operation has been cencelled before doing any work, and handily we have an isCancelled property, which can be checked as we enter the
main function. Here is the minimal example:
Here we are subclassing
Operation, and when we initialize the class we set a property for a
stringToPrint. When the
Operation is called from the
main() function is called which checks to see if the operation is cancelled, and if it sn't print the message "Hello, World!". Incidently, before adding this
printOperation to the queue I've added a
completionBlock that prints a Done! message when we've finished.
We can check that the cancelling mechianism works by cancelling the operation before adding it to the queue using
printOperation.cancel(), which sets the
isCancelled boolean to true.
This example uses
queue.addOperations([blockOperation], waitUntilFinished: true) but
queue.addOperation(blockOperation) would do just as well.
You should remember, since you can cancel an operation with a reference to that operation that it only makes sense to add an
Operation to a single
OperationQueue. If not you get a rather nasty
A dependency means that we can ensure one
Operation takes place before a subsequent operation. Cool beans.
From this point onwards, we are going to use the cusom subclassed operation to be able to make use of one of the benefits of using
Extending our example of using print we can set up a dependency between two
By dependency we mean that an
Operation object cannot execute until all of it's dependent operations have finished. The example here will show a single dependency to create a minimal example. (Yes, this uses default parameters, but the code is not too tricky) - we create a
SecondPrintOperation and a
FirstPrintOperation that are both almost the same as the
PrintOperation above - so they aren't doing anything to earth moving (I think) at this point. I'm going to whack these into an
operationQueue called multipleOperationQueue
We can therefore add a dependency with:
We have to make sure that the dependency isn’t cancelled, and do this by checking the dependencies of
SecondPrintOperation in the
Passing data between dependencies
In order to pass data between dependencies using an extra dependency that itself is a
BlockOperation, which means that no subclassing is required here.
There isn’t any real reason to use out existing
Operation classes to pass date, so imagine that the first class downloads some data and the second decodes it.
This decoding is similar to this article, so go there for more explaination of the decoding process.
Creating an adaptor
We can use the
BlockOperation to link properties within a
fetchOperation and a
decodeOperation allowing us to bridge in between the two. This adaptor looks like the following:
which of course relies on your
Operation objects (in this case) containing a
dataFetched property of the same type (this will be a
Getting data back from the object
Take a look at the implementation below to see how this is implemented
Implementing the adaptor
So here we are going to define the UserModel for the hard-coded data in a data object witch is just the
Stringrepresentation of a JSON String.
It is true that this implementation doesn’t actually download anything from an API call — but for this particular article that would be a little too in-depth as it would combine extra concepts. For no, hard coded Strings is absolutely fine.
Doing better: Creating a queue manager
The above solution kind of leaves the instantiation of the
OperationQueue up in the air. One way to solve this is to create a Singleton class (which also incorporates lazy variables) so this can be used safely in various parts of an application ensuring that
Operations are always added to the same queue.
QueueManager manages a particular queue
OperationQueue in space and time (although it could hold multiple queues, perhaps one which is for user-driven events and one used for system events).
This leaves a responsibility to create, configure and schedule operations on a queue and perhaps handle all networking events for a particular endpoint.
The queue manager will look something like the following:
Operations can be quite tricky, but are a great tool to use concurrency within your App, but their use should be weighed up against the potential use of GCD (which might be better for switching threads for example, due to ease of use).
As ever, put the tools in your toolbox and make the best decisions for your own project. Happy coding!
If you’ve any questions, comments or suggestions please hit me up on Twitter