Concurrency in Swift (Operations and Operation Queue Part 3)
In this part we will cover following topics
- What is Operations and It’s Life states
- Create Block, NSInvocationOperation , and Custom Operations to run tasks async
- How to cancel operations
- What is Operation Queues
- How to add Operations In Operation Queues
- How to create dependencies between Operations
- Benefits of Operation Queues Over GCD
- Dispatch Group Implementation Using Operation Queues
An operation object is an instance of the
Operation or NSOperation class (in the Foundation framework) that you use to encapsulate work you want your application to perform.
The Operation class itself is an abstract base class that must be subclassed in order to do any useful work. Despite being abstract, this class does provide a significant amount of infrastructure to minimize the amount of work you have to do in your own subclasses. In addition, the Foundation framework provides two concrete subclasses that you can use as-is with your existing code.
An operation has a state machine that represents its lifecycle. There are several possible states that occur at various parts of this lifecycle:
- When it’s been
instantiated, it will transition to the
- When we invoked the
startmethod, it will transition to the
- When the task
finished, it moves to
- When task is in progress and you call
cancel, then it will transition to the
isCancelledstate before moving onto the
There are mainly three ways to create operations
1. BlockOperation (Concrete Class)
A class you use as-is to execute one or more block objects concurrently. Because it can execute more than one block, a block operation object operates using a group semantic; only when all of the associated blocks have finished executing is the operation itself considered finished.. In block operation you can take advantage of operation dependencies, KVO, notifications and cancelling .
As shown in Figure 1 we executed this code
async which means it will return immediately but the bad news is it will block the main thread since
operation.start() was called on main thread
Operation objects execute in a synchronous manner by default — that is, they perform their task in the thread that calls their
What the heck is synchronous manner and execute one or more block objects concurrently.
As shown in Figure 1.0.1 as you can see the tasks/blocks added to the Block operation itself executed concurrently but the block run synchronous manner means it blocked the thread at which start is called in our case it is main thread
As shown in Figure Figure 1.0.2 , since we call start method on other thread , it will block that thread
As shown in Figure Figure 1.0.3, we can add completion block as well which will call when all concurrent blocks will executed
Run Block Operation Concurrently
As shown in Figure 1.1 since we call
start() method on a background thread it will perform their task in the thread. There is a cool way to do this using operation queue and we will see this later.
2. NSInvocationOperation (Concrete Class)
A class you use as-is to create an operation object based on an object and selector from your application.
In objective C we can create
NSInvocationOperation while it’s not available in Swift.
3. Custom Operations
Operationgives you complete control over the implementation of your own operations, including the ability to alter the default way in which your operation executes and reports its status.
As shown in Figure 2 we created custom operations by subclass it from
Operation base class and override its
main method. When you subclass you put your task on
main method. We implemented non concurrent custom operation and in this case we blocked the main thread
If you plan to execute operations manually and still want them to run asynchronously, you must take the appropriate actions to ensure that they do. You do this by defining your operation object as a concurrent operation.
As shown in Figure 3 we performed the following steps to perform task concurrently
- Created subclass
MyConcurrentQueue. Typo: The name should be
start()method will call
main()method on background thread
- On main method we defined our task and one thing to note we cater cancel case as well
- On calling
cancelon custom operation will transition to the
isCancelledstate and break the loop and as shown in Figure 3 it will print only 39487 items
- Operations Queues are Cocoa’s high-level abstraction on GCD
- Using Operation Queues you will see the real power of operations , instead of the starting the operation yourself , you give it to the operation queue it then handle the scheduling and execution.
- Operation Queues are an object-oriented way to encapsulate work that you want to perform asynchronously.
- You add
operation queueand we discussed how we can create operations by using two methods.
As shown in Figure 4 we created two operations (using Block) and added them into operation queue. Operation queue started both operation on some background thread and executed them. No need to call
start() method on custom thread 🆒. When we add operation to the operation queue it run as soon as it’s ready
As shown in Figure 5 we just executed task serially or you can say we implemented serial queue using Operation Queues please refer to my part 1 if you don’t know what is serial queue by setting
maxConcurrentOperationCount = 1
maxConcurrentOperationCount →The maximum number of queued operations that can execute at the same time. The default value is -1 which means let the system decide
maxConcurrentOperationCount = 2 we made a concurrent queue and now tasks are executing concurrently as shown in Figure 6
As shown in Figure 7 we again created a serial queue by adding dependencies between two tasks. We created two block operations and we are saying that don’t start task 1 until task 2 is finished by calling
Dispatch Group Implementation Using Operations Queue
In part 2 we used GCD dispatch group feature to block a thread until one or more tasks finished executing. As shown in Figure 8 we implemented the same behaviour using Operation Queues by using dependencies. This is very helpful if you cannot make progress until all of the specified tasks are completed.
As shown in Figure 8 we have three tasks and we wanted to run concurrently and when all the tasks finished we need to call some method to indicate that all tasks has finished and what we did
- Created a operation queue
- Created three block operations that will perform tasks
- Created a completion block operation (blockOperations4) which will trigger when all three tasks will finished
blockOperations3which means blockOperations4 will execute when all three tasks will finished
waitUntilFinished→ Blocks execution of the current thread until the operation object finishes its task since we don’t want to block the current thread which is main we assign it with false
- Run this code and “All Operation is Completed” will print when task1, task2 and task3 will finished
As shown in Figure 9 we just block the main thread by setting
waitUntilFinished = true. So the question is when it is helpful and you will get the answer in the next section
As shown in Figure 10 we implemented a dispatch group behavior using operation queue without using any dependencies what we did we used
waitUntilFinished feature appropriately . If you are on background thread you can block this thread to achieve this behaviour. I intentionally switched to background thread using
DispatchQueue.global().async method see part 1 to understand this code
We told operation queue run task 1, task 2 and task 3 on operation queue and block the current thread until these concurrent tasks will finish their execution
Benefits of Operation Queues Over GCD
- The Operation API provides support for dependencies. You can create complex dependencies between tasks very easily though in GCD you can achieve it but you have to do a lot of work.
- The NSOperation and NSOperationQueue classes have a number of properties that can be observed, using KVO (Key Value Observing). This is another important benefit if you want to monitor the state of an operation or operation queue.
- Operations can be paused, resumed, and cancelled. Once you dispatch a task using Grand Central Dispatch, you no longer have control or insight into the execution of that task. The NSOperation API is more flexible in that respect, giving the developer control over the operation’s life cycle
- The NSOperationQueue also adds a number of benefits to the mix. For example, you can specify the maximum number of queued operations that can run simultaneously. This makes it easy to control how many operations run at the same time or to create a serial operation queue.
In the next part, we will look at the actual use case of creating custom operation