/* Copyright (C) 2015 Apple Inc. All Rights Reserved. See LICENSE.txt for this sample’s licensing information Abstract: This file contains the foundational subclass of Operation. */ import Foundation /** The subclass of `Operation` from which all other operations should be derived. This class adds both Conditions and Observers, which allow the operation to define extended readiness requirements, as well as notify many interested parties about interesting operation state changes */ class EarthquakeOperation: Operation { // use the KVO mechanism to indicate that changes to "state" affect other properties as well override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { switch key { case "isExecuting", "isFinished", "isReady": return ["state"] default: return [] } } // MARK: State Management fileprivate enum State: Int, Comparable { /// The initial state of an `EarthquakeOperation`. case Initialized /// The `EarthquakeOperation` is ready to begin evaluating conditions. case Pending /// The `EarthquakeOperation` is evaluating conditions. case EvaluatingConditions /** The `EarthquakeOperation`'s conditions have all been satisfied, and it is ready to execute. */ case Ready /// The `EarthquakeOperation` is executing. case Executing /** Execution of the `EarthquakeOperation` has finished, but it has not yet notified the queue of this. */ case Finishing /// The `EarthquakeOperation` has finished executing. case Finished func canTransitionToState(target: State) -> Bool { switch (self, target) { case (.Initialized, .Pending): return true case (.Pending, .EvaluatingConditions): return true case (.EvaluatingConditions, .Ready): return true case (.Ready, .Executing): return true case (.Ready, .Finishing): return true case (.Executing, .Finishing): return true case (.Finishing, .Finished): return true default: return false } } } /** Indicates that the EarthquakeOperation can now begin to evaluate readiness conditions, if appropriate. */ func willEnqueue() { state = .Pending } /// Private storage for the `state` property that will be KVO observed. private var _state = State.Initialized /// A lock to guard reads and writes to the `_state` property private let stateLock = NSLock() private var state: State { get { return stateLock.withCriticalScope { _state } } set(newState) { /* It's important to note that the KVO notifications are NOT called from inside the lock. If they were, the app would deadlock, because in the middle of calling the `didChangeValueForKey()` method, the observers try to access properties like "isReady" or "isFinished". Since those methods also acquire the lock, then we'd be stuck waiting on our own lock. It's the classic definition of deadlock. */ willChangeValue(forKey: "state") stateLock.withCriticalScope { () -> Void in guard _state != .Finished else { return } assert(_state.canTransitionToState(target: newState), "Performing invalid state transition.") _state = newState } didChangeValue(forKey: "state") } } // Here is where we extend our definition of "readiness". override var isReady: Bool { switch state { case .Initialized: // If the operation has been cancelled, "isReady" should return true return isCancelled case .Pending: // If the operation has been cancelled, "isReady" should return true guard !isCancelled else { return true } // If super isReady, conditions can be evaluated if super.isReady { evaluateConditions() } // Until conditions have been evaluated, "isReady" returns false return false case .Ready: return super.isReady || isCancelled default: return false } } var userInitiated: Bool { get { return qualityOfService == .userInitiated } set { assert(state < .Executing, "Cannot modify userInitiated after execution has begun.") qualityOfService = newValue ? .userInitiated : .default } } override var isExecuting: Bool { return state == .Executing } override var isFinished: Bool { return state == .Finished } private func evaluateConditions() { assert(state == .Pending && !isCancelled, "evaluateConditions() was called out-of-order") state = .EvaluatingConditions OperationConditionEvaluator.evaluate(conditions: conditions, operation: self) { failures in self._internalErrors.append(contentsOf: failures) self.state = .Ready } } // MARK: Observers and Conditions private(set) var conditions = [OperationCondition]() func addCondition(condition: OperationCondition) { assert(state < .EvaluatingConditions, "Cannot modify conditions after execution has begun.") conditions.append(condition) } private(set) var observers = [EarthquakeOperationObserver]() func addObserver(observer: EarthquakeOperationObserver) { assert(state < .Executing, "Cannot modify observers after execution has begun.") observers.append(observer) } override func addDependency(_ operation: Operation) { assert(state < .Executing, "Dependencies cannot be modified after execution has begun.") super.addDependency(operation) } // MARK: Execution and Cancellation override final func start() { // Operation.start() contains important logic that shouldn't be bypassed. super.start() // If the operation has been cancelled, we still need to enter the "Finished" state. if isCancelled { finish() } } override final func main() { assert(state == .Ready, "This operation must be performed on an operation queue.") if _internalErrors.isEmpty && !isCancelled { state = .Executing for observer in observers { observer.operationDidStart(operation: self) } execute() } else { finish() } } /** `execute()` is the entry point of execution for all `EarthquakeOperation` subclasses. If you subclass `EarthquakeOperation` and wish to customize its execution, you would do so by overriding the `execute()` method. At some point, your `EarthquakeOperation` subclass must call one of the "finish" methods defined below; this is how you indicate that your operation has finished its execution, and that operations dependent on yours can re-evaluate their readiness state. */ func execute() { print("\(type(of: self)) must override `execute()`.") finish() } private var _internalErrors = [NSError]() func cancelWithError(error: NSError? = nil) { if let error = error { _internalErrors.append(error) } cancel() } final func produceOperation(operation: Operation) { for observer in observers { observer.operation(operation: self, didProduceOperation: operation) } } // MARK: Finishing /** Most operations may finish with a single error, if they have one at all. This is a convenience method to simplify calling the actual `finish()` method. This is also useful if you wish to finish with an error provided by the system frameworks. As an example, see `DownloadEarthquakesOperation` for how an error from an `URLSession` is passed along via the `finishWithError()` method. */ final func finishWithError(error: NSError?) { if let error = error { finish(errors: [error]) } else { finish() } } /** A private property to ensure we only notify the observers once that the operation has finished. */ private var hasFinishedAlready = false final func finish(errors: [NSError] = []) { if !hasFinishedAlready { hasFinishedAlready = true state = .Finishing let combinedErrors = _internalErrors + errors finished(errors: combinedErrors) for observer in observers { observer.operationDidFinish(operation: self, errors: combinedErrors) } state = .Finished } } /** Subclasses may override `finished(_:)` if they wish to react to the operation finishing with errors. For example, the `LoadModelOperation` implements this method to potentially inform the user about an error when trying to bring up the Core Data stack. */ func finished(errors: [NSError]) { // No op. } override final func waitUntilFinished() { /* Waiting on operations is almost NEVER the right thing to do. It is usually superior to use proper locking constructs, such as `dispatch_semaphore_t` or `dispatch_group_notify`, or even `NSLocking` objects. Many developers use waiting when they should instead be chaining discrete operations together using dependencies. To reinforce this idea, invoking `waitUntilFinished()` will crash your app, as incentive for you to find a more appropriate way to express the behavior you're wishing to create. */ fatalError("Waiting on operations is an anti-pattern. Remove this ONLY if you're absolutely sure there is No Other Way™.") } } // Simple operator functions to simplify the assertions used above. private func <(lhs: EarthquakeOperation.State, rhs: EarthquakeOperation.State) -> Bool { return lhs.rawValue < rhs.rawValue } private func ==(lhs: EarthquakeOperation.State, rhs: EarthquakeOperation.State) -> Bool { return lhs.rawValue == rhs.rawValue }