Advanced-NSOperations/Earthquakes/Operations/EarthquakeOperationQueue.swift

124 lines
5 KiB
Swift
Raw Permalink 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 an OperationQueue subclass.
*/
import Foundation
/**
The delegate of an `EarthquakeOperationQueue` can respond to `EarthquakeOperation`
lifecycle events by implementing these methods.
In general, implementing `EarthquakeOperationQueueDelegate` is not necessary; you would
want to use an `EarthquakeOperationObserver` instead. However, there are a couple of
situations where using `EarthquakeOperationQueueDelegate` can lead to simpler code.
For example, `GroupOperation` is the delegate of its own internal
`EarthquakeOperationQueue` and uses it to manage dependencies.
*/
@objc protocol EarthquakeOperationQueueDelegate: NSObjectProtocol {
@objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, willAddOperation operation: Operation)
@objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, operationDidFinish operation: Operation, withErrors errors: [NSError])
}
/**
`EarthquakeOperationQueue` is an `OperationQueue` subclass that implements a large
number of "extra features" related to the `EarthquakeOperation` class:
- Notifying a delegate of all operation completion
- Extracting generated dependencies from operation conditions
- Setting up dependencies to enforce mutual exclusivity
*/
class EarthquakeOperationQueue: OperationQueue {
weak var delegate: EarthquakeOperationQueueDelegate?
override func addOperation(_ operation: Operation) {
if let op = operation as? EarthquakeOperation {
// Set up a `BlockObserver` to invoke the `EarthquakeOperationQueueDelegate` method.
let delegate = BlockObserver(
startHandler: nil,
produceHandler: { [weak self] in
self?.addOperation($1)
},
finishHandler: { [weak self] in
if let q = self {
q.delegate?.operationQueue?(operationQueue: q, operationDidFinish: $0, withErrors: $1)
}
}
)
op.addObserver(observer: delegate)
// Extract any dependencies needed by this operation.
let dependencies = op.conditions.compactMap {
$0.dependencyForOperation(operation: op)
}
for dependency in dependencies {
op.addDependency(dependency)
self.addOperation(dependency)
}
/*
With condition dependencies added, we can now see if this needs
dependencies to enforce mutual exclusivity.
*/
let concurrencyCategories: [String] = op.conditions.compactMap { condition in
if !type(of: condition).isMutuallyExclusive { return nil }
return "\(type(of: condition))"
}
if !concurrencyCategories.isEmpty {
// Set up the mutual exclusivity dependencies.
let exclusivityController = ExclusivityController.sharedExclusivityController
exclusivityController.addOperation(operation: op, categories: concurrencyCategories)
op.addObserver(observer: BlockObserver(finishHandler: { operation, _ in
exclusivityController.removeOperation(operation: operation, categories: concurrencyCategories)
}))
}
/*
Indicate to the operation that we've finished our extra work on it
and it's now it a state where it can proceed with evaluating conditions,
if appropriate.
*/
op.willEnqueue()
}
else {
/*
For regular `EarthquakeOperation`s, we'll manually call out to the
queue's delegate we don't want to just capture "operation" because
that would lead to the operation strongly referencing itself and
that's the pure definition of a memory leak.
*/
operation.addCompletionBlock { [weak self, weak operation] in
guard let queue = self, let operation = operation else { return }
queue.delegate?.operationQueue?(operationQueue: queue, operationDidFinish: operation, withErrors: [])
}
}
delegate?.operationQueue?(operationQueue: self, willAddOperation: operation)
super.addOperation(operation)
}
override func addOperations(_ operations: [Operation], waitUntilFinished wait: Bool) {
/*
The base implementation of this method does not call `addOperation()`,
so we'll call it ourselves.
*/
for operation in operations {
addOperation(operation)
}
if wait {
for operation in operations {
operation.waitUntilFinished()
}
}
}
}