-
Star
(129)
You must be signed in to star a gist -
Fork
(19)
You must be signed in to fork a gist
-
-
Save calebd/93fa347397cec5f88233 to your computer and use it in GitHub Desktop.
| import Foundation | |
| /// An abstract class that makes building simple asynchronous operations easy. | |
| /// Subclasses must implement `execute()` to perform any work and call | |
| /// `finish()` when they are done. All `NSOperation` work will be handled | |
| /// automatically. | |
| open class AsynchronousOperation: Operation { | |
| // MARK: - Properties | |
| private let stateQueue = DispatchQueue( | |
| label: "com.calebd.operation.state", | |
| attributes: .concurrent) | |
| private var rawState = OperationState.ready | |
| @objc private dynamic var state: OperationState { | |
| get { | |
| return stateQueue.sync(execute: { rawState }) | |
| } | |
| set { | |
| willChangeValue(forKey: "state") | |
| stateQueue.sync( | |
| flags: .barrier, | |
| execute: { rawState = newValue }) | |
| didChangeValue(forKey: "state") | |
| } | |
| } | |
| public final override var isReady: Bool { | |
| return state == .ready && super.isReady | |
| } | |
| public final override var isExecuting: Bool { | |
| return state == .executing | |
| } | |
| public final override var isFinished: Bool { | |
| return state == .finished | |
| } | |
| public final override var isAsynchronous: Bool { | |
| return true | |
| } | |
| // MARK: - NSObject | |
| @objc private dynamic class func keyPathsForValuesAffectingIsReady() -> Set<String> { | |
| return ["state"] | |
| } | |
| @objc private dynamic class func keyPathsForValuesAffectingIsExecuting() -> Set<String> { | |
| return ["state"] | |
| } | |
| @objc private dynamic class func keyPathsForValuesAffectingIsFinished() -> Set<String> { | |
| return ["state"] | |
| } | |
| // MARK: - Foundation.Operation | |
| public override final func start() { | |
| super.start() | |
| if isCancelled { | |
| finish() | |
| return | |
| } | |
| state = .executing | |
| execute() | |
| } | |
| // MARK: - Public | |
| /// Subclasses must implement this to perform their work and they must not | |
| /// call `super`. The default implementation of this function throws an | |
| /// exception. | |
| open func execute() { | |
| fatalError("Subclasses must implement `execute`.") | |
| } | |
| /// Call this function after any work is done or after a call to `cancel()` | |
| /// to move the operation into a completed state. | |
| public final func finish() { | |
| state = .finished | |
| } | |
| } | |
| @objc private enum OperationState: Int { | |
| case ready | |
| case executing | |
| case finished | |
| } |
@diwu This pattern is known as "reader-writer" synchronization, allowing for concurrent reads, but all writes are synchronized. This is like the serial dispatch queue pattern, but is conceptually a little more efficient. The difference is only material in high-contention environments, but it doesn't hurt.
BTW, the reader-writer pattern is discussed in the latter part of WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Note, that video is using the old Swift 2 and Objective-C GCD syntax, but the idea is identical to what you see here.
By the way, I notice that this sample is doing the manual KVN of state. Because it’s a dynamic property, that is not needed. It does the KVN for you.
@calebd What's the licence of the this gist?
Thank you for sharing this.
I am learning to use the
Operationclass. Can I ask what the following is used for?// MARK: - NSObject @objc private dynamic class func keyPathsForValuesAffectingIsReady() -> Set<String> { return ["state"] } @objc private dynamic class func keyPathsForValuesAffectingIsExecuting() -> Set<String> { return ["state"] } @objc private dynamic class func keyPathsForValuesAffectingIsFinished() -> Set<String> { return ["state"] }
Here is Registering Dependent Keys
Do not call
super.start()from youroverrideofstart. As the documentation forstartsays (emphasis added):