@@ -30,6 +30,7 @@ extension EffectPublisher {
30
30
/// - Returns: A new effect that is capable of being canceled by an identifier.
31
31
public func cancellable( id: AnyHashable , cancelInFlight: Bool = false ) -> Self {
32
32
@Dependency ( \. navigationIDPath) var navigationIDPath
33
+
33
34
switch self . operation {
34
35
case . none:
35
36
return . none
@@ -47,49 +48,37 @@ extension EffectPublisher {
47
48
defer { _cancellablesLock. unlock ( ) }
48
49
49
50
if cancelInFlight {
50
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
51
- _cancellationCancellables [ cancelID] ? . forEach { $0. cancel ( ) }
51
+ _cancellationCancellables. cancel ( id: id, path: navigationIDPath)
52
52
}
53
53
54
54
let cancellationSubject = PassthroughSubject < Void , Never > ( )
55
55
56
- var cancellationCancellable : AnyCancellable !
57
- cancellationCancellable = AnyCancellable {
56
+ var cancellable : AnyCancellable !
57
+ cancellable = AnyCancellable {
58
58
_cancellablesLock. sync {
59
59
cancellationSubject. send ( ( ) )
60
60
cancellationSubject. send ( completion: . finished)
61
- for navigationIDPath in navigationIDPath. prefixes {
62
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
63
- _cancellationCancellables [ cancelID] ? . remove ( cancellationCancellable)
64
- if _cancellationCancellables [ cancelID] ? . isEmpty == . some( true ) {
65
- _cancellationCancellables [ cancelID] = nil
66
- }
67
- }
61
+ _cancellationCancellables. remove ( cancellable, at: id, path: navigationIDPath)
68
62
}
69
63
}
70
64
71
65
return publisher. prefix ( untilOutputFrom: cancellationSubject)
72
66
. handleEvents (
73
67
receiveSubscription: { _ in
74
68
_cancellablesLock. sync {
75
- for navigationIDPath in navigationIDPath. prefixes {
76
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
77
- _cancellationCancellables [ cancelID, default: [ ] ] . insert (
78
- cancellationCancellable
79
- )
80
- }
69
+ _cancellationCancellables. insert ( cancellable, at: id, path: navigationIDPath)
81
70
}
82
71
} ,
83
- receiveCompletion: { _ in cancellationCancellable . cancel ( ) } ,
84
- receiveCancel: cancellationCancellable . cancel
72
+ receiveCompletion: { _ in cancellable . cancel ( ) } ,
73
+ receiveCancel: cancellable . cancel
85
74
)
86
75
}
87
76
. eraseToAnyPublisher ( )
88
77
)
89
78
)
90
79
case let . run( priority, operation) :
91
80
return withEscapedDependencies { continuation in
92
- Self (
81
+ return Self (
93
82
operation: . run( priority) { send in
94
83
await continuation. yield {
95
84
await withTaskCancellation ( id: id, cancelInFlight: cancelInFlight) {
@@ -124,11 +113,10 @@ extension EffectPublisher {
124
113
public static func cancel( id: AnyHashable ) -> Self {
125
114
let dependencies = DependencyValues . _current
126
115
@Dependency ( \. navigationIDPath) var navigationIDPath
127
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
128
116
return Deferred { ( ) -> Publishers . CompactMap < Result < Action ? , Failure > . Publisher , Action > in
129
117
DependencyValues . $_current. withValue ( dependencies) {
130
118
_cancellablesLock. sync {
131
- _cancellationCancellables [ cancelID ] ? . forEach { $0 . cancel ( ) }
119
+ _cancellationCancellables. cancel ( id : id , path : navigationIDPath )
132
120
}
133
121
}
134
122
return Just < Action ? > ( nil )
@@ -222,28 +210,19 @@ extension EffectPublisher {
222
210
operation: @Sendable @escaping ( ) async throws -> T
223
211
) async rethrows -> T {
224
212
@Dependency ( \. navigationIDPath) var navigationIDPath
213
+
225
214
let ( cancellable, task) = _cancellablesLock. sync { ( ) -> ( AnyCancellable , Task < T , Error > ) in
226
215
if cancelInFlight {
227
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
228
- _cancellationCancellables [ cancelID] ? . forEach { $0. cancel ( ) }
216
+ _cancellationCancellables. cancel ( id: id, path: navigationIDPath)
229
217
}
230
218
let task = Task { try await operation ( ) }
231
219
let cancellable = AnyCancellable { task. cancel ( ) }
232
- for navigationIDPath in navigationIDPath. prefixes {
233
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
234
- _cancellationCancellables [ cancelID, default: [ ] ] . insert ( cancellable)
235
- }
220
+ _cancellationCancellables. insert ( cancellable, at: id, path: navigationIDPath)
236
221
return ( cancellable, task)
237
222
}
238
223
defer {
239
224
_cancellablesLock. sync {
240
- for navigationIDPath in navigationIDPath. prefixes {
241
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
242
- _cancellationCancellables [ cancelID] ? . remove ( cancellable)
243
- if _cancellationCancellables [ cancelID] ? . isEmpty == . some( true ) {
244
- _cancellationCancellables [ cancelID] = nil
245
- }
246
- }
225
+ _cancellationCancellables. remove ( cancellable, at: id, path: navigationIDPath)
247
226
}
248
227
}
249
228
do {
@@ -259,28 +238,19 @@ extension EffectPublisher {
259
238
operation: @Sendable @escaping ( ) async throws -> T
260
239
) async rethrows -> T {
261
240
@Dependency ( \. navigationIDPath) var navigationIDPath
241
+
262
242
let ( cancellable, task) = _cancellablesLock. sync { ( ) -> ( AnyCancellable , Task < T , Error > ) in
263
243
if cancelInFlight {
264
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
265
- _cancellationCancellables [ cancelID] ? . forEach { $0. cancel ( ) }
244
+ _cancellationCancellables. cancel ( id: id, path: navigationIDPath)
266
245
}
267
246
let task = Task { try await operation ( ) }
268
247
let cancellable = AnyCancellable { task. cancel ( ) }
269
- for navigationIDPath in navigationIDPath. prefixes {
270
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
271
- _cancellationCancellables [ cancelID, default: [ ] ] . insert ( cancellable)
272
- }
248
+ _cancellationCancellables. insert ( cancellable, at: id, path: navigationIDPath)
273
249
return ( cancellable, task)
274
250
}
275
251
defer {
276
252
_cancellablesLock. sync {
277
- for navigationIDPath in navigationIDPath. prefixes {
278
- let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
279
- _cancellationCancellables [ cancelID] ? . remove ( cancellable)
280
- if _cancellationCancellables [ cancelID] ? . isEmpty == . some( true ) {
281
- _cancellationCancellables [ cancelID] = nil
282
- }
283
- }
253
+ _cancellationCancellables. remove ( cancellable, at: id, path: navigationIDPath)
284
254
}
285
255
}
286
256
do {
@@ -336,9 +306,9 @@ extension Task where Success == Never, Failure == Never {
336
306
/// - Parameter id: An identifier.
337
307
public static func cancel< ID: Hashable & Sendable > ( id: ID ) {
338
308
@Dependency ( \. navigationIDPath) var navigationIDPath
339
- let cancelID = _CancelID ( id : id , navigationIDPath : navigationIDPath )
309
+
340
310
return _cancellablesLock. sync {
341
- _cancellationCancellables [ cancelID ] ? . forEach { $0 . cancel ( ) }
311
+ _cancellationCancellables. cancel ( id : id , path : navigationIDPath )
342
312
}
343
313
}
344
314
@@ -354,22 +324,19 @@ extension Task where Success == Never, Failure == Never {
354
324
}
355
325
356
326
@_spi ( Internals) public struct _CancelID : Hashable {
327
+ let discriminator : ObjectIdentifier
357
328
let id : AnyHashable
358
329
let navigationIDPath : NavigationIDPath
359
330
360
331
init ( id: AnyHashable , navigationIDPath: NavigationIDPath ) {
332
+ self . discriminator = ObjectIdentifier ( type ( of: id. base) )
361
333
self . id = id
362
334
self . navigationIDPath = navigationIDPath
363
335
}
364
-
365
- public init ( _id id: AnyHashable ) {
366
- self . id = id
367
- self . navigationIDPath = NavigationIDPath ( )
368
- }
369
336
}
370
337
371
- @_spi ( Internals) public var _cancellationCancellables : [ _CancelID : Set < AnyCancellable > ] = [ : ]
372
- @ _spi ( Internals ) public let _cancellablesLock = NSRecursiveLock ( )
338
+ @_spi ( Internals) public var _cancellationCancellables = CancellablesCollection ( )
339
+ private let _cancellablesLock = NSRecursiveLock ( )
373
340
374
341
@rethrows
375
342
private protocol _ErrorMechanism {
@@ -390,19 +357,56 @@ extension _ErrorMechanism {
390
357
391
358
extension Result : _ErrorMechanism { }
392
359
360
+ @_spi ( Internals)
361
+ public class CancellablesCollection {
362
+ var storage : [ _CancelID : Set < AnyCancellable > ] = [ : ]
393
363
364
+ func insert(
365
+ _ cancellable: AnyCancellable ,
366
+ at id: AnyHashable ,
367
+ path: NavigationIDPath
368
+ ) {
369
+ for navigationIDPath in path. prefixes {
370
+ let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
371
+ self . storage [ cancelID, default: [ ] ] . insert ( cancellable)
372
+ }
373
+ }
394
374
395
- /*
375
+ func remove(
376
+ _ cancellable: AnyCancellable ,
377
+ at id: AnyHashable ,
378
+ path: NavigationIDPath
379
+ ) {
380
+ for navigationIDPath in path. prefixes {
381
+ let cancelID = _CancelID ( id: id, navigationIDPath: navigationIDPath)
382
+ self . storage [ cancelID] ? . remove ( cancellable)
383
+ if self . storage [ cancelID] ? . isEmpty == true {
384
+ self . storage [ cancelID] = nil
385
+ }
386
+ }
387
+ }
396
388
397
- [1, 2, 3], TimerID
398
- [1]
389
+ func cancel(
390
+ id: AnyHashable ,
391
+ path: NavigationIDPath
392
+ ) {
393
+ let cancelID = _CancelID ( id: id, navigationIDPath: path)
394
+ self . storage [ cancelID] ? . forEach { $0. cancel ( ) }
395
+ self . storage [ cancelID] = nil
396
+ }
397
+
398
+ func exists(
399
+ at id: AnyHashable ,
400
+ path: NavigationIDPath
401
+ ) -> Bool {
402
+ return self . storage [ _CancelID ( id: id, navigationIDPath: path) ] != nil
403
+ }
399
404
400
- Trie<AnyID, [AnyHashable: Set<AnyCancellable>]>
401
- .insert(navigationID, [:])
402
- .modify(navigationID, default: [:]) {
403
- $0
404
- }
405
+ public var count : Int {
406
+ return self . storage. count
407
+ }
405
408
406
- trie[navigationID, default: [:]][id, default: []].insert(cancellable)
407
- trie[navigationID] = nil
408
- */
409
+ public func removeAll( ) {
410
+ self . storage. removeAll ( )
411
+ }
412
+ }
0 commit comments