// Created by Vasily Ulianov on 09.02.17, updated in 2019. | |
// License: MIT | |
import Foundation | |
/// Subclass of `Operation` that adds support of asynchronous operations. | |
/// 1. Call `super.main()` when override `main` method. | |
/// 2. When operation is finished or cancelled set `state = .finished` or `finish()` | |
open class AsynchronousOperation: Operation { | |
public override var isAsynchronous: Bool { | |
return true | |
} | |
public override var isExecuting: Bool { | |
return state == .executing | |
} | |
public override var isFinished: Bool { | |
return state == .finished | |
} | |
public override func start() { | |
if self.isCancelled { | |
state = .finished | |
} else { | |
state = .ready | |
main() | |
} | |
} | |
open override func main() { | |
if self.isCancelled { | |
state = .finished | |
} else { | |
state = .executing | |
} | |
} | |
public func finish() { | |
state = .finished | |
} | |
// MARK: - State management | |
public enum State: String { | |
case ready = "Ready" | |
case executing = "Executing" | |
case finished = "Finished" | |
fileprivate var keyPath: String { return "is" + self.rawValue } | |
} | |
/// Thread-safe computed state value | |
public var state: State { | |
get { | |
stateQueue.sync { | |
return stateStore | |
} | |
} | |
set { | |
let oldValue = state | |
willChangeValue(forKey: state.keyPath) | |
willChangeValue(forKey: newValue.keyPath) | |
stateQueue.sync(flags: .barrier) { | |
stateStore = newValue | |
} | |
didChangeValue(forKey: state.keyPath) | |
didChangeValue(forKey: oldValue.keyPath) | |
} | |
} | |
private let stateQueue = DispatchQueue(label: "AsynchronousOperation State Queue", attributes: .concurrent) | |
/// Non thread-safe state storage, use only with locks | |
private var stateStore: State = .ready | |
} |
This comment has been minimized.
This comment has been minimized.
works like a charm! thanks |
This comment has been minimized.
This comment has been minimized.
Awesome Dood! |
This comment has been minimized.
This comment has been minimized.
There is an issue in the steps that you provided for using this code. I made the same mistake, which can be found in my question over on Stack Overflow. According to Apple's documentation, you should not call |
This comment has been minimized.
This comment has been minimized.
Cool! and thanks @nickkohrn for your valuable comment! |
This comment has been minimized.
This comment has been minimized.
@nickkohrn, maybe you don't need to call |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
that if else in main() though... 4 lines of code waisted |
This comment has been minimized.
This comment has been minimized.
Why? You need to flag operation as |
This comment has been minimized.
This comment has been minimized.
Awesome thanks Sorix. I made one example project to understand also. |
This comment has been minimized.
This comment has been minimized.
If you are looking for threadsafety solution, consider this: |
This comment has been minimized.
This comment has been minimized.
@Sorix think he meant that you could have written |
This comment has been minimized.
This comment has been minimized.
I updated my code to make it a thread-safe one. Please update applications that use that code to avoid cryptic fatal errors like In spite of that code is a thread-safe, please don't crush your application if you use a non-thread safe code inside your operation's completion or inside an operation, e.g.: var someArray = [Int]()
let queue = OperationQueue()
for _ in 1...10000 {
// For test purposes I've added `finish()` inside that async operation to finish that operation
let operation = AsynchronousOperation()
operation.completionBlock = {
// We concurrently modifing a shared array from all operations
// Setting data is not thread-safe, at some point we will receive a crash
someArray.append(1)
}
queue.addOperation(operation)
}
queue.waitUntilAllOperationsAreFinished()
print(someArray) P.S. Thanks to @DanSkeel, @zhihuitang who noted that. |
This comment has been minimized.
This comment has been minimized.
As per the Apple Documentation, you will need only main function to operate the synchronous operation. For asynchronous operation, you are required to override start function and instance properties(isAsynchronous, isExecuting and isFinished). Also, you need to call the functions for asynchronous behaviour from start function. But here its not mentioned anything about the same. |
This comment has been minimized.
This comment has been minimized.
I overrode the methods you mentioned. You could call async code anywhere you want, it's up to you to override methods in your operation. |
This comment has been minimized.
very useful, thanks!