-
Notifications
You must be signed in to change notification settings - Fork 102
/
OperationQueue.swift
128 lines (108 loc) · 4.95 KB
/
OperationQueue.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*
Copyright (C) 2015 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
Abstract:
This file contains an NSOperationQueue subclass.
*/
import Foundation
public typealias PSOperationQueueDelegate = OperationQueueDelegate
/**
The delegate of an `OperationQueue` can respond to `Operation` lifecycle
events by implementing these methods.
In general, implementing `OperationQueueDelegate` is not necessary; you would
want to use an `OperationObserver` instead. However, there are a couple of
situations where using `OperationQueueDelegate` can lead to simpler code.
For example, `GroupOperation` is the delegate of its own internal
`OperationQueue` and uses it to manage dependencies.
*/
@objc public protocol OperationQueueDelegate: NSObjectProtocol {
@objc optional func operationQueue(_ operationQueue: OperationQueue, willAddOperation operation: Foundation.Operation)
@objc optional func operationQueue(_ operationQueue: OperationQueue, operationDidFinish operation: Foundation.Operation, withErrors errors: [Error])
}
public typealias PSOperationQueue = OperationQueue
/**
`OperationQueue` is an `NSOperationQueue` subclass that implements a large
number of "extra features" related to the `Operation` class:
- Notifying a delegate of all operation completion
- Extracting generated dependencies from operation conditions
- Setting up dependencies to enforce mutual exclusivity
*/
open class OperationQueue: Foundation.OperationQueue {
open weak var delegate: OperationQueueDelegate?
override open func addOperation(_ operation: Foundation.Operation) {
if let op = operation as? Operation {
// Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method.
let delegate = BlockObserver(
startHandler: nil,
produceHandler: { [weak self] in
self?.addOperation($1)
},
finishHandler: { [weak self] finishedOperation, errors in
if let q = self {
q.delegate?.operationQueue?(q, operationDidFinish: finishedOperation, withErrors: errors)
}
}
)
op.addObserver(delegate)
// Extract any dependencies needed by this operation.
let dependencies = op.conditions.compactMap {
$0.dependencyForOperation(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
guard type(of: condition).isMutuallyExclusive else { return nil }
return "\(type(of: condition))"
}
if !concurrencyCategories.isEmpty {
// Set up the mutual exclusivity dependencies.
let exclusivityController = ExclusivityController.sharedExclusivityController
exclusivityController.addOperation(op, categories: concurrencyCategories)
op.addObserver(BlockObserver(finishHandler: { operation, _ in
exclusivityController.removeOperation(operation, categories: concurrencyCategories)
}))
}
} else {
/*
For regular `NSOperation`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?(queue, operationDidFinish: operation, withErrors: [])
}
}
delegate?.operationQueue?(self, willAddOperation: operation)
super.addOperation(operation)
/*
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.
*/
if let op = operation as? Operation {
op.didEnqueue()
}
}
override open func addOperations(_ ops: [Foundation.Operation], waitUntilFinished wait: Bool) {
/*
The base implementation of this method does not call `addOperation()`,
so we'll call it ourselves.
*/
for operation in ops {
addOperation(operation)
}
if wait {
for operation in ops {
operation.waitUntilFinished()
}
}
}
}