Advanced-NSOperations/Earthquakes/Operations/OperationCondition.swift

110 lines
3.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Copyright (C) 2015 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
This file contains the fundamental logic relating to EarthquakeOperation conditions.
*/
import Foundation
let OperationConditionKey = "OperationCondition"
/**
A protocol for defining conditions that must be satisfied in order for an
operation to begin execution.
*/
protocol OperationCondition {
/**
The name of the condition. This is used in userInfo dictionaries of `.ConditionFailed`
errors as the value of the `OperationConditionKey` key.
*/
static var name: String { get }
/**
Specifies whether multiple instances of the conditionalized operation may
be executing simultaneously.
*/
static var isMutuallyExclusive: Bool { get }
/**
Some conditions may have the ability to satisfy the condition if another
operation is executed first. Use this method to return an operation that
(for example) asks for permission to perform the operation
- Parameter operation: The `EarthquakeOperation` to which the Condition has been added.
- Returns: An `Operation`, if a dependency should be automatically added. Otherwise, `nil`.
- Note: Only a single operation may be returned as a dependency. If you
find that you need to return multiple operations, then you should be
expressing that as multiple conditions. Alternatively, you could return
a single `GroupOperation` that executes multiple operations internally.
*/
func dependencyForOperation(operation: EarthquakeOperation) -> Operation?
/// Evaluate the condition, to see if it has been satisfied or not.
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void)
}
/**
An enum to indicate whether an `OperationCondition` was satisfied, or if it
failed with an error.
*/
enum OperationConditionResult: Equatable {
case Satisfied
case Failed(NSError)
var error: NSError? {
if case .Failed(let error) = self {
return error
}
return nil
}
}
func ==(lhs: OperationConditionResult, rhs: OperationConditionResult) -> Bool {
switch (lhs, rhs) {
case (.Satisfied, .Satisfied):
return true
case (.Failed(let lError), .Failed(let rError)) where lError == rError:
return true
default:
return false
}
}
// MARK: Evaluate Conditions
struct OperationConditionEvaluator {
static func evaluate(conditions: [OperationCondition], operation: EarthquakeOperation, completion: @escaping ([NSError]) -> Void) {
// Check conditions.
let conditionGroup = DispatchGroup()
var results: [OperationConditionResult?] = Array(repeating: nil, count: conditions.count)
// Ask each condition to evaluate and store its result in the "results" array.
for (index, condition) in conditions.enumerated() {
conditionGroup.enter()
condition.evaluateForOperation(operation: operation) { result in
results[index] = result
conditionGroup.leave()
}
}
// After all the conditions have evaluated, this block will execute.
conditionGroup.notify(queue: DispatchQueue.global(qos: .default)) {
// Aggregate the errors that occurred, in order.
var failures = results.compactMap { $0?.error }
/*
If any of the conditions caused this operation to be cancelled,
check for that.
*/
if operation.isCancelled {
failures.append(NSError(code: .ConditionFailed))
}
completion(failures)
}
}
}