Tags

nsoperation

NSOperation and NSOperationQueue are built on top of GCD and provide additional features built-in that are very hard to achieve using just GCD. This post will provide basic introduction of NSOperation and how to create a simple custom NSOperation and execute it. In my next post on this series, I will delve into NSOperationQueue and how to use your custom NSOperation in conjunction with NSOperationQueue to satisfy your different scheduling requirements.

NSOperation

NSOperation is an abstract class that provides a way to consolidate the data required and the code associated with a task. In order to create a custom operation, we should derive from the NSOperation class and add the data and code associated with our custom operation. Alternatively we can use one of the 2 Apple-provided subclasses of NSOperation: NSBlockOperation and NSInvocationOperation. I will not go into details of these specific subclasses of NSOperation here.

Features of NSOperation

NSOperation, in spite of being an abstract class, provides wealth of features associated with executing a task. The following are the two most important features which , though not impossible, are very messy to implement with GCD:

Tracking state of an task

The states that a task wrapped in NSOperation can take are “Ready”, “Executing” and “Finished”

Ability to cancel an operation.

NSOperation provides the ability to cancel an operation by cancel() method.

At the core, the cancel() method just sets a boolean cancelled = true. For any of the custom subclasses of NSOperation, the onus is on us to ensure that we account for the value of the cancelled boolean and gracefully exit our task.

Will go into more details further in the blog

Below I will list the steps that go into encompassing a task into an NSOperation. For simplicity, the example I am going to describe below will execute synchronously ( i.e When the code execution finishes the task finishes (or) task does not launch any asynchronous code as part of its logic)

custom NSOperation Example

Let’s say our gazelle(or deer) is in the forest grazing. A single unit of task that the gazelle needs to perform is checkForPredators(). In short, the gazelle looks up, checks its surrounding and will be done with the task.

In this scenario,

the data associated with the task would be the gazelle itself and

the logic associated with the task would be the call to “checkForPredators()”

In this most straightforward scenario, the new task only needs to override the main() method of the NSOperation abstract class. Below are the 3 things to remember when overriding main():

Ensure you are setting self.executing = true before starting task logic in main()

checkForPredators(gazelle)// Check for predators done when this func returns

self.executing=false

}

}

The simplest way to start the execution of a custom subclass of NSOperation is to call the start() method. When we look at the open source code for NSOperation ( in Swift Yay!!) here, we notice that the start() method is as below

NSOperation start()

Swift

1

2

3

4

public funcstart(){

main()

finish()// ensures the state is updated to finished = true

}

Hence, when we call GazelleCheckPredatorsOperation.start(), our custom implementation of the main() is called and then finish(), which cleans up the state of the Operation as soon as the main() function call returns.

CONGRATULATIONS !!!! you now have successfully created the basic NSOperation and started it to perform a pre-defined task!!!

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)

Fire AS1, Wait for AS1 to return, If AS1 succeeds only then start AS2 potentially using data from AS1.

Fire AS1, Fire AS2 (The firing of AS2 is independent of return value of AS1)

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.

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.

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

Asynchronous NSOperation

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

classAsynchronousOperation: NSOperation{

enumState{

caseReady,Executing,Finished

private keyPath: String{

return"is"+self.rawValue

}

}

overrideasynchronous{

returntrue

}

varstate=State.Ready{

willSet{

willChangeValueForKey(self)

willChangeValueForKey(newValue)

}

didSet{

didChangeValueForKey(oldValue)

didChangeValueForKey(self)

}

}

overrideexecuting{

returnstate==.Executing

}

overridefinished{

returnstate==.Finished

}

overridefuncstart(){

ifself.cancelled{

state=.Finished

}else{

state=.Ready

main()

}

}

overridemain(){

ifself.cancelled{

state=.Finished

}else{

state=.Executing

//Asynchronous logic (eg: n/w calls) with callback {

// if self.cancelled {

// state = .Finished

// } else {

// Perform any final operations on results // returned in asynchronous operation

// state = Finished

// }

// } --&gt; Completion of callback block

}

}

}

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.

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:

NSOperationQueue initialization

Swift

1

2

3

4

5

6

letopQueue=NSOperationQueue()

opQueue.maxConcurrentOperationCount=1

letasyncOperation1=AsynchronousOperation()

letasyncOperation2=AsynchronousOperation()

opQueue.addOperation(asyncOperation1)

opQueue.addOperation(asyncOperation2)

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.