Concurrency in Swift (Custom Operations Part 4)

Ali Akhtar
8 min readSep 26, 2020
https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2

As shown in Figure 1 we created two operations and we added both in operation queue, In addition to this, we added a dependency rule that say starts operation 2 after finishes operation 1. As we know the previous blog, the operation queue automatically runs the operation on another thread

When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread. Therefore, if you always run operations by adding them to an operation queue, there is no reason to make them asynchronous.

Figure 1

As shown in Figure 2 even though we added the dependency rule still, task 2 started and the reason is that task 1 dispatch its task on another queue and return immediately and the operation queue assume that it finishes that’s why it started task2 , and in a real application, we faced this issue a lot since we used NSUrlSession that executes its task on another queue so te question is how can we solve this problem

Figure 2

We can solve this problem by changing the operation state manually. The NSOperation class provides the basic logic to track the execution state of your operation automatically but since our task is dispatching on another queue, the operation queue needs to know when the task actually finished

Operation State

The operation has a more complex lifecycle then Dispatch groups. This gives you greater control. Operation objects maintain state information internally to determine when it is safe to execute and also to notify external clients of the progression through the operation’s life cycle. Your custom subclasses maintains this state information to ensure the correct execution of operations in your code. The key paths associated with an operation’s states are:

isReady →The isReady key path lets clients know when an operation is ready to execute. The ready property contains the value true when the operation is ready to execute now or false if there are still unfinished operations on which it is dependent. In most cases, you do not have to manage the state of this key path yourself. If the readiness of your operations is determined by factors other than dependent operations, however — such as by some external condition in your program — you can provide your own implementation of the ready property and track your operation’s readiness yourself. It is often simpler though just to create operation objects only when your external state allows it.

isExecuting →T he isExecuting key path lets clients know whether the operation is actively working on its assigned task. The executing the property must report the value true if the operation is working on its task or false if it is not.

isFinished → The isFinished key path lets clients know that the operation finished its task successfully or was canceled and is exiting. An operation object does not clear a dependency until the value at the isFinished key path changes to true. Similarly, an operation queue does not dequeue an operation until the finished property contains the value true. Thus, marking operations as finished is critical to keeping queues from backing up with in-progress or canceled operations.

isCancelled →The isCancelled key path lets clients know that the cancellation of operation was requested. Support for cancellation is voluntary but encouraged and your own code should not have to send KVO notifications for this key path

Summary

When operation starts its state progresses from isReady to isExecuting. If the task is asynchronous like download an image, it sends the call to the network and returns immediately. It looks like it finished. It’s no longer doing any work on the current thread but the async task is running on the background thread. You need a way to manually set the operation state to isExecuting until it really finishes.

Operation State property are read only. You can’t set them directly , So hhow you manage their values for an async operation . You must do something to cause the operation state property to return the correct value. The operation class relies on , KVO (key value observation) to send the notification for state

Create an Asynchronous Operation

As shown in Figure 3 we created reusable async operation. Following thing to be consider

  1. First, we created a state enum that maintains the state of our async operation. The operation state property is read-only, you can’t set them directly Instead you’ll create a state property for your async operation, then use this property to manage the base class operation states properties. Your async operation needs a variable property to manage its state
  2. An asyn operation default initial state value is ready. Now we can override base class state properties to use new state property. Apple document says don’t override isReady , the operation itself decides when the operation is Ready by examining its dependencies . In most cases, you do not have to manage the state of this isReady keypath yourself. If the readiness of your operations is determined by factors other than dependent operations, however — such as by some external condition in your program — you can provide your own implementation of the ready property and track your operation’s readiness yourself. It is often simpler though just to create operation objects only when your external state allows it.
  3. You just need to set the operation state properties isExecuting and isFinished to use your new state
  4. When implementing async operation object you must implment isAsynchronous property and return true. This property is only used if you run the operation manually outside of an operation queue. It ensures the operation runs off the main thread.
  5. Operation uses KVO to keep track of its state so your async operation must send KVO notifications whenever its state values change because it’s property is Read-only. We added property observers on state property and called willChangeValue and didChangeValue to its base class to update its state. In willSet() we inform the operation queue that the property for the current state and the next state will change. In didSet we then inform the operation queue that the property for the previous state and the new state has changed.
  6. KVO mission accomplished.
  7. Since your async operation must trigger KVO notification for the base class properties which has is Prefix. so we created computed properties keypath
  8. Next, we need to override the operation start and cancel method The start method checks if the operation has been canceled and set our async operation state to finished. If the operation is not canceled called the main function members this is an async operation that returns immediately, so you need to manually set its state to executing
  9. Do not call super.start() from your override of start. As the documentation for start says If you are implementing a concurrent operation, you must override this method and use it to initiate your operation. Your custom implementation must not call super at any time.
Figure 3

As shown in Figure 4 Now in specific async operation we just need to override main and called finished. Now as you can see operation 2 starts after the operation 2 finishes irrespective of its task executed on another thread. This is one of the use cases of using custom/subclass operation. In addition to this since we add operation on operation queue, operation queue as usual use another thread to run the main method of operation

Figure 4

As shown in Figure 5 operation queue uses one thread since these operations are dependent it smartly can use one thread for these tasks since it is dependent. Note thread reference can be swap but it will ask only one thread from shared pools of thread

Figure 5

As shown in Figure 6, we removed dependency then you can see it asks for two threads. Now you can imagine how smart it is

Figure 6

Thread Safety

As shown in the previous example one bug can be thatstate is not thread-safe, setting state might cause Thread Sanitizer issues. Also, Apply suggests that the overridden property must also provide the status in a thread-safe manner. As shown in Figure 7 we make state property atomic manner. If you don’t know what is a barrier please see previous part

Figure 7

As shown in Figure 8.1 and 8.2 we provided queue to operation queue and you can see ourProvidedOperationQueueCustomQueue is used by operation queue for dispatching its task.

underlyingQueue → The dispatch queue used to execute operations.The default value of this property is nil. You can set the value of this property to an existing dispatch queue to have enqueued operations interspersed with blocks submitted to that dispatch queue.The value of this property should only be set if there are no operations in the queue; setting the value of this property when operationCount is not equal to 0 raises an invalidArgumentException. The value of this property must not be the value returned by dispatch_get_main_queue(). The quality-of-service level set for the underlying dispatch queue overrides any value set for the operation queue's qualityOfService property.

Figure 8.1
Figure 8.2

As shown in Figure 9 setting the value of underlyingQueue property when operation count is not equal to 0 (means after we added operation to the operation queue) raises an invalidArgumentException

Figure 8

Useful links

Thread example is taken from this link

--

--