How to Use Multithreaded Operations: Operation, Operation Queue, Operation Dependencies
Keep your app responsive to user interactions. Once we build an app user interface above all our aim to be a more responsive interface.
Imagine some users, when they are tapping the button and the button downloads some files or an image. the download task should not block the user interaction otherwise users kill the app or push to the background state.
Things get complicated quickly when your application needs to do more than a few tasks. There is not much time to do heavy work in the main work cycle and still provide a responsive user interface.
Getting Started
In order, I will explain:’
- “What is the Operation?”’
- “Operation Queue”’
- “Operation Dependencies”’
- “KVO-Compliant Properties”’
And then I will reinforce the issue with a few examples.
What Is an Operation?
Operation is an abstract class that represents the code and data associated with a single task. The Operation
class is an abstract class, you do not use it directly but instead, subclass or use one of the system-defined subclasses (NSInvocationOperation
or BlockOperation
) to perform the actual task.
An operation object is a single-shot object — that is, it executes its task once and cano.not be used to execute it again. You typically execute operations by adding them to an operation queue (an instance of the OperationQueue
class). An operation queue executes its operations either directly, by running them on secondary threads, or indirectly using the libdispatch
library (also known as Grand Central Dispatch). For more information about how queues execute operations, see OperationQueue
.
Operation Queue
An operation queue executes its queued Operation
objects based on their priority and readiness. After being added to an operation queue, an operation remains in its queue until it reports that it is finished with its task. You can’t directly remove an operation from a queue after it has been added.
Operation Dependencies
Dependencies are a convenient way to execute operations in a specific order. You can add and remove dependencies for an operation using the addDependency(_:)
and removeDependency(_:)
methods. By default, an operation object that has dependencies is not considered ready until all of its dependent operation objects have finished executing. Once the last dependent operation finishes, however, the operation object becomes ready and able to execute.
KVO-Compliant Properties
The NSOperation
class is key-value coding (KVC) and key-value observing (KVO) compliant for several of its properties. As needed, you can observe these properties to control other parts of your application. To observe the properties, use the following key paths:
isCancelled
- read-only — return “the transaction been canceled?”
open var isCancelled: Bool { get }
isAsynchronous
- read-only — return “is the operation concurrent?”
@available(iOS 7.0, *)open var isAsynchronous: Bool { get }
isExecuting
- read-only — return when the operation is on the progress
open var isExecuting: Bool { get }
isFinished
- read-only — return operation is a finished status
open var isFinished: Bool { get }
isReady
- read-only — return when an operation is ready
open var isReady: Bool { get }
dependencies
- read-only — add one or more dependent task, it's related to each other
open var dependencies: [Operation] { get }
queuePriority
- readable and writable — Determines the order of operations in the queue.
open var queuePriority: Operation.QueuePriority
Qperation Queue Priority
completionBlock
- readable and writable — AWESOME BLOCK :) triggers the completion block
Subclassing Notes
The Operation
class provides the basic logic to track the execution state of your operation but otherwise must be subclassed to do any real work. How you create your subclass depends on whether your operation is designed to execute concurrently or non-concurrently.
Now, I will create an Operation
class, that responsible for the download tasks. You can do this as series or as parallel if you want.
class DownloadOperation : Operation { private var task : URLSessionDownloadTask! enum OperationState : Int { case ready case executing case finished } private var state : OperationState = .ready { willSet { self.willChangeValue(forKey: “isExecuting”) self.willChangeValue(forKey: “isFinished”) } didSet { self.didChangeValue(forKey: “isExecuting”) self.didChangeValue(forKey: “isFinished”) } } override var isReady: Bool { return state == .ready } override var isExecuting: Bool { return state == .executing } override var isFinished: Bool { return state == .finished } init(session: URLSession, downloadTaskURL: URL, completionHandler: ((URL?, URLResponse?, Error?) -> Void)?) { super.init() task = session.downloadTask(with: downloadTaskURL, completionHandler: { [weak self] (url, response, error) in if let completionHandler = completionHandler { completionHandler(url, response, error) } self?.state = .finished }) } override func start() { if(isCancelled) { state = .finished return } print(“executing the task: \ (self.task.originalRequest?.url?.absoluteString ?? “”)”) self.task.resume() state = .executing } override func cancel() { super.cancel() self.task.cancel() }}
The Operation task has three states: ready, execution and finished and has overridden.
And finally, I will call the operation class and it downloads a file from the web.
let firstUrl = URL(string:"https://via.placeholder.com/150/56a8c2")!
let secondUrl = URL(string:"https://via.placeholder.com/150/771796")!
let thirdUrl = URL(string:"https://via.placeholder.com/150/24f355")!let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.name = "Image Download queue"let firstOperation = DownloadOperation(session: URLSession.shared, downloadTaskURL: firstUrl, completionHandler: { (localURL, response, error) in print("finished the task: \(firstUrl.absoluteString)")})let secondOperation = DownloadOperation(session: URLSession.shared, downloadTaskURL: secondUrl, completionHandler: { (localURL, response, error) inprint("finished the task: \(secondUrl.absoluteString)")})let thirdOperation = DownloadOperation(session: URLSession.shared, downloadTaskURL: thirdUrl, completionHandler: { (localURL, response, error) inprint("finished the task: \(secondUrl.absoluteString)")})queue.addOperation(firstOperation)
queue.addOperation(secondOperation)
queue.addOperation(thirdOperation)
Result:
All tasks are performing as a parallel because I increase the maxConcurrentOperationCount. If you want to perform the tasks as a serial, of course, you can set maxConcurrentOperationCount to 1.
maxConcurrentOperationCount: The maximum number of queued operations that can execute at the same time.
And the new result would be like the following.
Making Use of Dependencies
And one more step. In some case, you can wish the second operation depend on the first operation but all operations are performing as parallel.
secondOperation.addDependency(firstOperation)
queue.addOperation(firstOperation
queue.addOperation(secondOperation)
queue.addOperation(thirdOperation)
Conclusion
I hope you have understood the whole of the guide. The Operation Queue resolves more problem which you have faced.
Some open-source projects I have created.