Concurrency in Swift (Operations and Operation Queue Part 3)

Ali Akhtar
8 min readJun 23, 2019

--

https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2

This is the continuation of the concurrency in swift series. See part1 and part 2 to understand the basics

In this part we will cover following topics

  1. What is Operations and It’s Life states
  2. Create Block, NSInvocationOperation , and Custom Operations to run tasks async
  3. How to cancel operations
  4. What is Operation Queues
  5. How to add Operations In Operation Queues
  6. How to create dependencies between Operations
  7. Benefits of Operation Queues Over GCD
  8. Dispatch Group Implementation Using Operation Queues

Operations

Operations are an object-oriented way to encapsulate work that you want to perform asynchronously. Operations are designed to be used either in conjunction with an operation queue or by themselves

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.

Operations State

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 isReady state.
  • When we invoked the start method, it will transition to the isExecuting state.
  • When the task finished , it moves to isFinished
  • When task is in progress and you call cancel , then it will transition to the isCancelled state before moving onto the isFinished state

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 start method.

Figure 1

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

Figure 1.0.1

As shown in Figure Figure 1.0.2 , since we call start method on other thread , it will block that thread

Figure 1.0.2

As shown in Figure Figure 1.0.3, we can add completion block as well which will call when all concurrent blocks will executed

Figure 1.0.3

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.

Figure 1.1

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.

https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW6

3. Custom Operations

Subclassing Operation gives 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

Figure 2

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

  1. Created subclass MyConcurrentQueue . Typo: The name should be MyConcurrentOperations
  2. Calling start() method will call main() method on background thread
  3. On main method we defined our task and one thing to note we cater cancel case as well
  4. On calling cancel on custom operation will transition to the isCancelled state and break the loop and as shown in Figure 3 it will print only 39487 items
Figure 3

Operation Queues

  1. Operations Queues are Cocoa’s high-level abstraction on GCD
  2. 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.
  3. Operation Queues are an object-oriented way to encapsulate work that you want to perform asynchronously.
  4. You add operations (tasks/work) on operation queue and we discussed how we can create operations by using two methods.

Add Operations

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

Figure 4

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

Figure 5

By setting maxConcurrentOperationCount = 2 we made a concurrent queue and now tasks are executing concurrently as shown in Figure 6

Figure 6

Operations Dependencies

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 blockOperations1.addDependency(blockOperations2)

Figure 7

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

  1. Created a operation queue
  2. Created three block operations that will perform tasks
  3. Created a completion block operation (blockOperations4) which will trigger when all three tasks will finished
  4. Made blockOperations4 dependent on blockOperations1, blockOperations2 and blockOperations3 which means blockOperations4 will execute when all three tasks will finished
  5. 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
  6. Run this code and “All Operation is Completed” will print when task1, task2 and task3 will finished
Figure 8

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

Figure 9

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

Figure 10

Benefits of Operation Queues Over GCD

  1. 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.
  2. 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.
  3. 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
  4. 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.

Upcoming

In the next part, we will look at the actual use case of creating custom operation

Useful Links

· https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW1

--

--