Asynchronous NSOperation: Why and how?

why-so-asynchronous

Asynchronous is a usually a scary concept for beginners. When I initially started my foray into iOS development ( my first development experience actually), I was very much intimidated about the prospect of me having to do asynchronous programming. But as I started to familiarize myself with iOS concepts and Swift language, I have increasingly noticed Apple has made it very easy for iOS app developers to use Asynchronous programming.

There are 3 kinds of asynchronous scenarios that you would want to build in your app (AS1, AS2  == Asynchronous operation 1, Asynchronous operation 2)

  1. Fire AS1, Wait for AS1 to return, If AS1 succeeds only then start AS2 potentially using data from AS1.
  2. Fire AS1, Fire AS2 (The firing of AS2 is independent of return value of AS1)
  3. Fire AS1, Wait for AS1 to return, fire AS2 ( Here too, the firing of AS2 is independent of AS1, but sometimes we want to restrict the number of active asynchronous operations)

We have many amazing open source libraries that simplifies coding for scenario 1. Eg: http://reactivecocoa.io/

For scenario 2, we can use the following steps ( this can be achieved in different ways. Describing one way briefly here, I believe this can be another blog post in itself)

  • Create a NSOperationQueue with appropriate specifications
  • Create NSURLSession with the delegate set to the operation queue created above
  • dataTask1 = NSURLSession.dataTaskWithRequest(NSURLRequest for of the AS1 task). dataTask1.start()
  • dataTask2 = NSURLSession.dataTaskWithRequest(NSURLRequest for of the AS2 task). dataTask2.start()

In the above scenario dataTask2 is kicked off even before dataTask1 returns.

Scenario 3 is what I will be focussing on in my post.

The Apple’s framework class I will be using for this are below:

  1. NSOperationQueue
  2. NSOperation

NSOperation basics

I can’t go about explaining how to implement an Asynchronous NSOperation without describing few basics about the built-in framework class NSOperation. Below are some of the important points we need to be aware of

  • NSOperation is an abstract class that provides a way to represent the data and the logic associated with a single task ( The task can be as simple/complex as you like)
  • It contains a lot of built-in logic that “coordinates the safe execution of your task” – as the Apple documentation puts it
  • So we can derive from NSOperation class, put the logic of the task by overriding main() of NSOperation, create an operation object and class start() to execute the code.
  • NSoperation keeps track of the state of the task. A task at any given time can be in 3 different states.
    • Ready
    • Executing
    • Finished ( Completed/Cancelled)
  • Below image is my attempt to create a flowchart of how the NSOperation works.

Screen Shot 2016-03-06 at 4.04.53 PM

Why doesnt this work for Asynchronous task logic?

The state tracking mechanism of nsoperation that I have described above works with the assumption that the custom task logic is complete as soon as the execution is out of main() function. In other words, the nsoperation is built under the assumption that the custom logic is NOT asynchronous.

Aysnchronous NSOperation

The NSOperation documentation guides us on what NSoperation functions/properties needs to be overriden if our task logic is asynchronous:

  • asynchronous (override to return true)
  • executing
  • finished
  • start

What this suggests is that we need manually keep track of the state of our subclass of NSOperation. We also need to ensure we are firing all the necessary KVO ( Key Value Observation) events associated with the state change. the Ray Wenderlich tutorial on “Asynchronous Operations” introduced me to the

Below is the code for the state and overriding of the suggested properties and methods

In the above code we create a custom variable state ( of the custom enum State type). This is initially set to .Ready as we use the in-built method calls willChangeKeyValue and the didChangekeyValue to fire the necessary KVO events whose utility we will explore shortly.

In the beginning of both the start() and main() method we check if the operation has been cancelled and prevent any further execution. In order to begin execution of operation, create an instance of AsynchronousOperation and call start() on it. This operation’s status will not be set to .Finished until the asynchronous operation completes and executes the callback block as specified in the code above.

Let’s re-write scenario 3 once again:

Fire asynchronous operation 1, Wait for asynchronous operation1 to return, fire asynchronous operation2

For achieving this, we need an “NSOperationQueue” which, as the name suggests, queues up NSOperation objects and schedules them depending on the queue specifications and the status of the objects. This NSOperationQueue makes use of the KVO of the operation state in determining the operation state and scheduling the next one in the queue.

In our case, we need only one operation to be scheduled at a time and the second one to be scheduled only of the first one finishes ( either successful or failed or cancelled).  So, when setting up the NSOperationQueue we set its maxConcurrentOperationCount to be 1 as below:

Due to the state management and KVO we have authored, we are guaranteed that the asyncOperation2 is executed only when the first operation is finished.

Summary

We have created a custom subclass of NSOperation to ensure that we can track an asynchronous operation status utilizing the existing NSOperation state concepts. This NSOperation is then scheduled on an NSOperationQueue with maxConcurrentOperationCount = 1, to ensure that we have only one active asynchronous operation at anytime.

Image