blob: 44e8e791f30c2263cdb7a3114d0de029d2406995 (
plain)
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
//
// AsyncOperation.swift
// MullvadVPN
//
// Created by pronebird on 01/06/2020.
// Copyright © 2020 Mullvad VPN AB. All rights reserved.
//
import Foundation
/// A base implementation of an asynchronous operation
class AsyncOperation: Operation, OperationProtocol {
/// A state transaction lock used to perform critical sections of code within `start`, `cancel`
/// and `finish` calls.
fileprivate let transactionLock = NSRecursiveLock()
/// A state lock used for manipulating the operation state flags in a thread safe fashion.
fileprivate let stateLock = NSRecursiveLock()
/// The operation observers.
fileprivate var observers: [AnyOperationObserver<AsyncOperation>] = []
/// Operation state flags.
private var _isExecuting = false
private var _isFinished = false
private var _isCancelled = false
final override var isExecuting: Bool {
return stateLock.withCriticalBlock { _isExecuting }
}
final override var isFinished: Bool {
return stateLock.withCriticalBlock { _isFinished }
}
final override var isCancelled: Bool {
return stateLock.withCriticalBlock { _isCancelled }
}
final override var isAsynchronous: Bool {
return true
}
final override func start() {
transactionLock.withCriticalBlock {
if self.isCancelled {
self.finish()
} else {
self.observers.forEach { $0.operationWillExecute(self) }
self.setExecuting(true)
self.main()
}
}
}
override func main() {
// Override in subclasses
}
/// Cancel operation
/// Subclasses should override `operationDidCancel` instead
final override func cancel() {
transactionLock.withCriticalBlock {
if self.isCancelled {
super.cancel()
} else {
self.setCancelled(true)
super.cancel()
// Only notify the operation about cancellation when it is already running,
// otherwise the call to `start` should automatically `finish()` the operation.
if self.isExecuting {
self.operationDidCancel()
}
}
}
}
/// Override in subclasses to support task cancellation.
/// Subclasses should call `finish()` to complete the operation
func operationDidCancel() {
// no-op
}
final func finish() {
transactionLock.withCriticalBlock {
guard !self.isFinished else { return }
self.stateLock.withCriticalBlock {
self.observers.forEach { $0.operationWillFinish(self) }
}
if self.isExecuting {
self.setExecuting(false)
}
self.setFinished(true)
self.stateLock.withCriticalBlock {
self.observers.forEach { $0.operationDidFinish(self) }
}
}
}
private func setExecuting(_ value: Bool) {
willChangeValue(for: \.isExecuting)
stateLock.withCriticalBlock { _isExecuting = value }
didChangeValue(for: \.isExecuting)
}
private func setFinished(_ value: Bool) {
willChangeValue(for: \.isFinished)
stateLock.withCriticalBlock { _isFinished = value }
didChangeValue(for: \.isFinished)
}
private func setCancelled(_ value: Bool) {
willChangeValue(for: \.isCancelled)
stateLock.withCriticalBlock { _isCancelled = value }
didChangeValue(for: \.isCancelled)
}
// MARK: - Observation
/// Add type-erased operation observer
fileprivate func addAnyObserver(_ observer: AnyOperationObserver<AsyncOperation>) {
stateLock.withCriticalBlock {
self.observers.append(observer)
}
}
}
/// This extension exists because Swift has some issues with infering the associated type in `OperationObserver`
extension OperationProtocol where Self: AsyncOperation {
func addObserver<T: OperationObserver>(_ observer: T) where T.OperationType == Self {
let transform = TransformOperationObserver<AsyncOperation>(observer)
let wrapped = AnyOperationObserver(transform)
addAnyObserver(wrapped)
}
}
protocol OperationSubclassing {
/// Use this method in subclasses or extensions where you would like to synchronize
/// the class members access using the same lock used for guarding from race conditions
/// when managing operation state.
func synchronized<T>(_ body: () -> T) -> T
}
extension AsyncOperation: OperationSubclassing {
func synchronized<T>(_ body: () -> T) -> T {
return stateLock.withCriticalBlock(body)
}
}
|