I started to work with iOS in 2008, back in the days there was no GCD, no RxSwift or similar solutions. NSURLSession was a dream, and the only thing available was NSURLConnection. Even blocks were not available at the time.
Starting with this article, I will discuss some of the old ways of working in Cocoa and how to apply them in Swift. Today I will talk about
NSOperation (or Operation in Swift), probably the most important class for concurrency back in the days, which seems to be forgotten these days.
NSOperation and why it was so important in the early days?
When iOS was released, there were a couple of ways to do asynchronous computation (excluding the low-level C ways): NSThread and NSOperation. So
NSOperation was a daily class to deal with, if you did not want to deal with threads directly, and before
NSURLSession it was necessary to have background network calls without headaches.
NSOperation is an abstract class, as described by the official documentation:
An abstract class that represents the code and data associated with a single task.
This class requires to have the task coded in the
main method and offers a
start method to setup the process and do the necessary computation upfront, remember: blocks were not available back in the days. A great advantage of
NSOperation is the fact that has also a queue associated, named
NSOperationQueue. This class is responsible for ensuring that operations are performed in the right order and without exceeding the maximum number of concurrent operations. Back in the days, network connections were a way slower than today and perform 4-5-6 requests at the same time was not a great idea and were subject to timeouts (for example when requesting images). The only viable way to avoid this was to make sure requests were limited in number.
Another great thing is the fact that a single operation in the queue can have a lower or higher priority and this was a great plus when dealing with important requests like retrieving data and less important like sending analytics data or crash reports back.
I am still habit to use
NSOperation in my apps, especially in the ones were external dependencies are limited or completely forbidden. One way to use
Operation in Swift, is to make sure requests are limited and performed in no-max of a certain number at the same time.
Let’s see how this works…
I want to create a remote logging API, which logs things directly in the server (no local storage), so a logging text is sent immediately to the server for testing or debugging purposes.
If the number of logs is high and the number of users is high as well, the risk to harm the server is high, so limiting the requests from a single device is a good way to balance the load.
LogOperation is pretty easy:
text is our logging text and a
POST request sends it inside a json string containing just the text. The request points to the configured
After the definition of the general information, an instance of
URLSession and a dedicate
OperationQueue are more than welcomed.
Note: I am not focusing on making the class extensible or scalable, in a production code I would split this code in multiple classes and make them easier to combine.
After this, it’s time to make the operation more flexible in Swift using
isExecuting. These two properties are essential to the life-cycle of the operation and are used by the system to understand when an operation completes and when is running. So to access these properties, we need to a trick in Swift:
isFinished is essential, as stated in the documentation:
The isFinished key path lets clients know that an operation finished its task successfully or was cancelled 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 isFinished property contains the value true. Thus, marking operations as finished is critical to keeping queues from backing up with in-progress or cancelled operations.
The isExecuting key path lets clients know whether the operation is actively working on its assigned task. The isExecuting property must report the value true if the operation is working on its task or false if it is not.
Both properties have to be managed if the operation override the
If you replace the start() method or your operation object, you must also replace the isFinished/isExecuting property and generate KVO notifications when the operation finishes executing or is cancelled.
Why replacing/overriding the
start method instead of the
main method in this case? The
main method is performed in a background thread and would wait until we reach the end of the method before completing the operation. Using this method with
URLSession would then require a usage of a semaphore to wait for its completion, things that can be avoided using
There’s also the
isAsynchronous that indicates an operation is performed asynchronous, in this case it’s not necessary to override this property becasue the example uses a queue:
When you add an operation to an operation queue, the queue ignores the value of the isAsynchronous 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.
Time to write the
The previous code does essentially 3 things:
- Setup of the operation with early exit if cancelled
- Creation of the request
- Creation and start of the data task
In this way, we can have a simple helper method to log directly from the
which can then be used in the following way:
With this approach, there’s a control on which request should be performed before, using priorities.
Operation is a simple, but powerful class that seems to be forgotten, but it’s still a great tool that is there when we need to create asynchronous code. I would not recommend to use the code I showed before as it is in a production app, but I use a similar concept and more advanced network strategy in most of the apps where I can’t use third party libraries like RxSwift.
In the next article, I will extend the discussion about
OperationQueue showing how to create dependency graphs and make an operation depending to others.