What is NSOperationQueue or NSOperation in SWIFT ?
NSOperation is an abstract class which can’t be used directly so you have to use NSOperation subclasses. In the iOS SDK, we are provided with two concrete subclasses of NSOperation. These classes can be used directly, but you can also subclass NSOperation and create your own class to perform the operations.
The two classes that we can use directly are:
NSBlockOperation – Use this class to initiate operation with one or more blocks. The operation itself can contain more than one block and the operation will be considered as finish when all blocks are executed.
NSInvocationOperation – Use this class to initiate an operation that consists of invoking a selector on a specified object.
So what’s the advantages of NSOperation?
First, they support dependencies through the method addDependency(op: NSOperation) in the NSOperation class. When you need to start an operation that depends on the execution of the other, you will want to use NSOperation.
NSOperation Illustration Secondly, you can change the execution priority by setting the property queuePriority with one of these values:
public enum NSOperationQueuePriority : Int
{
case VeryLow
case Low
case Normal
case High
case VeryHigh
}
The operations with high priority will be executed first.
You can cancel a particular operation or all operations for any given queue. The operation can be cancelled after being added to the queue. Cancellation is done by calling method cancel() in the NSOperation class. When you cancel any operation,
we have three scenarios that one of them will happen:
-Your operation is already finished. In that case, the cancel method has no effect.
-Your operation is already being executing. In that case, system will NOT force your operation code to stop but instead, cancelled property will be set to true.
-Your operation is still in the queue waiting to be executed. In that case, your operation will not be executed.
NSOperation has 3 helpful boolean properties which are finished, cancelled, and ready.
Finished will be set to true once operation execution is done.
Cancelled is set to true once the operation has been cancelled.
Ready is set to true once the operation is about to be executed now.
Any NSOperation has an option to set completion block to be called once the task being finished. The block will be called once the property finished is set to true in NSOperation.
Please take a look at the below code, we use NSOperationQueues.
First declare the variable below in the ViewController class:
var queue = NSOperationQueue()
Next, replace the didClickOnStart method with the code below and see how we perform operations in NSOperationQueue:
@IBAction func didClickOnStart(sender: AnyObject) {
queue = NSOperationQueue()
queue.addOperationWithBlock { () -> Void in
let FirstImage = Downloader.downloadImageWithURL(imageURLs[0])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView1.image = FirstImage
})
}
queue.addOperationWithBlock { () -> Void in
let SecondImage = Downloader.downloadImageWithURL(imageURLs[1])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView2.image = SecondImage
})
}
queue.addOperationWithBlock { () -> Void in
let ThirdImage = Downloader.downloadImageWithURL(imageURLs[2])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView3.image = ThirdImage
})
}
queue.addOperationWithBlock { () -> Void in
let FourthImage = Downloader.downloadImageWithURL(imageURLs[3])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView4.image = FourthImage
})
}
}
As you see in the above code, you use the method addOperationWithBlock to create a new operation with the given block (or as we call it in Swift, a closure). It’s very simple, isn’t it? To perform a task in the main queue, instead of calling dispatch_async() as when using GCD, we can do the same from NSOperationQueue (NSOperationQueue.mainQueue()) and submit the operation you want to execute in the main queue.
You can run the app to have a quick test. If the code was entered correctly, the app should be able to download the images in background without blocking the UI.
In the previous example, we used the method addOperationWithBlock to add operation in the queue. Let’s see how we can use NSBlockOperation to do the same, but at the same time, giving us more functionalities and options such as setting completion handler. The didClickOnStart method is rewritten like this:
@IBAction func didClickOnStart(sender: AnyObject) {
queue = NSOperationQueue()
let operation1 = NSBlockOperation(block: {
let FirstImage = Downloader.downloadImageWithURL(imageURLs[0])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView1.image = FirstImage
})
})
operation1.completionBlock = {
print("Operation 1 completed")
}
queue.addOperation(operation1)
let operation2 = NSBlockOperation(block: {
let SecondImage = Downloader.downloadImageWithURL(imageURLs[1])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView2.image = SecondImage
})
})
operation2.completionBlock = {
print("Operation 2 completed")
}
queue.addOperation(operation2)
let operation3 = NSBlockOperation(block: {
let ThirdImage = Downloader.downloadImageWithURL(imageURLs[2])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView3.image = ThirdImage
})
})
operation3.completionBlock = {
print("Operation 3 completed")
}
queue.addOperation(operation3)
let operation4 = NSBlockOperation(block: {
let FourthImage = Downloader.downloadImageWithURL(imageURLs[3])
NSOperationQueue.mainQueue().addOperationWithBlock({
self.imageView4.image = FourthImage
})
})
operation4.completionBlock =
{
print("Operation 4 completed")
}
queue.addOperation(operation4)
}
For each operation, we create a new instance of NSBlockOperation to encapsulate the task into a block. By using NSBlockOperation, you’re allowed to set the completion handler. Now when the operation is done, the completion handler will be called.
Canceling Operations
NSBlockOperation allows you to manage the operations. Now let’s see how to cancel an operation. To do that, first add a bar button item to the navigation bar and name it Cancel. In order to demonstrate the cancel operation, we will add a dependency between Operation #2 and Operation #1, and another dependency between Operation #3 and Operation #2. This means Operation #2 will start after finishing of Operation #1, and Operation #3 will start after Operation #2 completes. Operation #4 has no dependency and will work concurrently. To cancel the operations all you need to do is call cancelAllOperations() of NSOperationQueue. Insert the following method in the ViewController class:
@IBAction func didClickOnCancel(sender: AnyObject) {
self.queue.cancelAllOperations()
}
Remember you need to associate the Cancel button you added to the navigation bar with the didClickOnCancel method. You can do this by returning to the Main.storyboard file and opening the Connections Inspector. There you will see the unlink didSelectCancel() in the Received Actions section. Click + drag from the empty circle to the Cancel bar button. Then create the dependencies in the didClickOnStart method like this:
operation2.addDependency(operation1)
operation3.addDependency(operation2)
Next change the completion block of operation #1 to log the cancelled state like this:
operation1.completionBlock = {
print("Operation 1 completed, cancelled:\(operation1.cancelled) ")
}
You may change the log statement for operation #2, #3 and #4, so you will have a better idea of the process. Now let’s build and run. After you hit the Start button, press the Cancel button. This will cancel all operations after operation #1 completes. Here is what happens:
As operation #1 already executed, cancelling will do nothing. This is why the cancelled value is logged false, and the app still shows image #1.
If you hit the Cancel button quick enough, operation #2 is cancelled. The cancelAllOperations() call will stop its execution, so image #2 is not downloaded.
Operation #3 was already in the queue, waiting for operation #2 to complete. As it depends on the completion of operation #2 and operation #2 was cancelled, operation #3 will not be executed and kicked out immediately from the queue.
There is no dependency configured for operation #4. It just runs concurrently to download image #4.
To dive further into iOS concurrency, I advise you to check out Apple’s Concurrency Guide.
Discover more from CODE t!ps
Subscribe to get the latest posts sent to your email.