@@ -112,11 +112,19 @@ class ConnectionsMonitor: SynchronizationSubscriber {
112112
113113/// Defines a cancellable `Operation` subclass to allow for cancellable network requests.
114114private class CancellableNetworkOperation : Operation {
115+ /// The current in-flight network request
115116 private var task : URLSessionDataTask ? = nil
117+
118+ /// The completion closure to invoke once the request is completed
116119 private let completion : ConnectionNetworkController . ConnectionResponseClosure
120+
121+ /// The network controller to perform requests with
117122 private weak var networkController : ConnectionNetworkController ?
123+
124+ /// The request to perform
118125 private let request : Connection . Request
119126
127+ /// Creates an instance of `CancellableNetworkOperation`.
120128 init ( networkController: ConnectionNetworkController ,
121129 request: Connection . Request ,
122130 _ completion: @escaping ConnectionNetworkController . ConnectionResponseClosure ) {
@@ -125,17 +133,83 @@ private class CancellableNetworkOperation: Operation {
125133 self . completion = completion
126134 }
127135
136+ /// State for this operation.
137+ @objc private enum OperationState : Int {
138+ case ready
139+ case executing
140+ case finished
141+ }
142+
143+ /// Concurrent queue for synchronizing access to `state`.
144+ private let stateQueue = DispatchQueue ( label: " com.ifttt.connections_monitor.rw.state " , attributes: . concurrent)
145+
146+ /// Private backing stored property for `state`.
147+ private var _state : OperationState = . ready
148+
149+ /// The state of the operation
150+ @objc private dynamic var state : OperationState {
151+ get {
152+ return stateQueue. sync { _state }
153+ }
154+ set {
155+ stateQueue. async ( flags: . barrier) { self . _state = newValue }
156+ }
157+ }
158+
159+ override var isReady : Bool {
160+ return state == . ready && super. isReady
161+ }
162+
163+ override var isExecuting : Bool {
164+ return state == . executing
165+ }
166+
167+ override var isFinished : Bool {
168+ return state == . finished
169+ }
170+
171+ override var isAsynchronous : Bool {
172+ return true
173+ }
174+
175+ open override class func keyPathsForValuesAffectingValue( forKey key: String ) -> Set < String > {
176+ let keys = [ " isReady " , " isFinished " , " isExecuting " ]
177+ if keys. contains ( key) {
178+ return . init( arrayLiteral: #keyPath( state) )
179+ }
180+
181+ return super. keyPathsForValuesAffectingValue ( forKey: key)
182+ }
183+
184+ override func start( ) {
185+ if isCancelled {
186+ state = . finished
187+ return
188+ }
189+
190+ state = . executing
191+
192+ main ( )
193+ }
194+
128195 override func main( ) {
129- task = networkController? . start ( request: request, completion: { response in
196+ task = networkController? . start ( request: request, completion: { [ weak self] response in
197+ guard let self = self else { return }
130198 self . completion ( response)
131- self . completionBlock ? ( )
199+ self . finish ( )
132200 } )
133201 task? . resume ( )
134202 }
135203
136204 override func cancel( ) {
137- super. cancel ( )
138205 task? . cancel ( )
206+ finish ( )
139207 }
140208
209+ /// Call this method to finish the operation
210+ private func finish( ) {
211+ if !isFinished {
212+ state = . finished
213+ }
214+ }
141215}
0 commit comments