1
- import Combine
1
+ @ preconcurrency import Combine
2
2
import Foundation
3
3
4
4
extension Effect {
@@ -49,34 +49,35 @@ extension Effect {
49
49
AnyPublisher < Action , Never > , PassthroughSubject < Void , Never >
50
50
>
51
51
> in
52
- _cancellablesLock. lock ( )
53
- defer { _cancellablesLock. unlock ( ) }
54
-
55
- if cancelInFlight {
56
- _cancellationCancellables. cancel ( id: id, path: navigationIDPath)
57
- }
58
-
59
- let cancellationSubject = PassthroughSubject < Void , Never > ( )
60
-
61
- var cancellable : AnyCancellable !
62
- cancellable = AnyCancellable {
63
- _cancellablesLock. sync {
64
- cancellationSubject. send ( ( ) )
65
- cancellationSubject. send ( completion: . finished)
66
- _cancellationCancellables. remove ( cancellable, at: id, path: navigationIDPath)
52
+ _cancellationCancellables. withValue {
53
+ if cancelInFlight {
54
+ $0. cancel ( id: id, path: navigationIDPath)
67
55
}
68
- }
69
56
70
- return publisher. prefix ( untilOutputFrom: cancellationSubject)
71
- . handleEvents (
72
- receiveSubscription: { _ in
73
- _cancellablesLock. sync {
74
- _cancellationCancellables. insert ( cancellable, at: id, path: navigationIDPath)
57
+ let cancellationSubject = PassthroughSubject < Void , Never > ( )
58
+
59
+ let cancellable = LockIsolated < AnyCancellable ? > ( nil )
60
+ cancellable. setValue (
61
+ AnyCancellable {
62
+ _cancellationCancellables. withValue {
63
+ cancellationSubject. send ( ( ) )
64
+ cancellationSubject. send ( completion: . finished)
65
+ $0. remove ( cancellable. value!, at: id, path: navigationIDPath)
75
66
}
76
- } ,
77
- receiveCompletion: { _ in cancellable. cancel ( ) } ,
78
- receiveCancel: cancellable. cancel
67
+ }
79
68
)
69
+
70
+ return publisher. prefix ( untilOutputFrom: cancellationSubject)
71
+ . handleEvents (
72
+ receiveSubscription: { _ in
73
+ _cancellationCancellables. withValue {
74
+ $0. insert ( cancellable. value!, at: id, path: navigationIDPath)
75
+ }
76
+ } ,
77
+ receiveCompletion: { _ in cancellable. value!. cancel ( ) } ,
78
+ receiveCancel: cancellable. value!. cancel
79
+ )
80
+ }
80
81
}
81
82
. eraseToAnyPublisher ( )
82
83
)
@@ -101,7 +102,7 @@ extension Effect {
101
102
/// - Parameter id: An effect identifier.
102
103
/// - Returns: A new effect that will cancel any currently in-flight effect with the given
103
104
/// identifier.
104
- public static func cancel< ID : Hashable > ( id: ID ) -> Self {
105
+ public static func cancel( id: some Hashable & Sendable ) -> Self {
105
106
let dependencies = DependencyValues . _current
106
107
@Dependency ( \. navigationIDPath) var navigationIDPath
107
108
// NB: Ideally we'd return a `Deferred` wrapping an `Empty(completeImmediately: true)`, but
@@ -110,8 +111,8 @@ extension Effect {
110
111
// trickery to make sure the deferred publisher completes.
111
112
return . publisher { ( ) -> Publishers . CompactMap < Just < Action ? > , Action > in
112
113
DependencyValues . $_current. withValue ( dependencies) {
113
- _cancellablesLock . sync {
114
- _cancellationCancellables . cancel ( id: id, path: navigationIDPath)
114
+ _cancellationCancellables . withValue {
115
+ $0 . cancel ( id: id, path: navigationIDPath)
115
116
}
116
117
}
117
118
return Just < Action ? > ( nil ) . compactMap { $0 }
@@ -163,26 +164,27 @@ extension Effect {
163
164
/// - operation: An async operation.
164
165
/// - Throws: An error thrown by the operation.
165
166
/// - Returns: A value produced by operation.
166
- public func withTaskCancellation< ID : Hashable , T > (
167
- id: ID ,
167
+ public func withTaskCancellation< T : Sendable > (
168
+ id: some Hashable & Sendable ,
168
169
cancelInFlight: Bool = false ,
169
170
isolation: isolated ( any Actor ) ? = #isolation,
170
- operation: sending @escaping @isolated ( any ) ( ) async throws -> sending T
171
+ operation: @escaping @Sendable ( ) async throws -> T
171
172
) async rethrows -> T {
172
173
@Dependency ( \. navigationIDPath) var navigationIDPath
173
174
174
- let ( cancellable, task) = _cancellablesLock. sync { ( ) -> ( AnyCancellable , Task < T , Error > ) in
175
- if cancelInFlight {
176
- _cancellationCancellables. cancel ( id: id, path: navigationIDPath)
175
+ let ( cancellable, task) : ( AnyCancellable , Task < T , Error > ) = _cancellationCancellables
176
+ . withValue {
177
+ if cancelInFlight {
178
+ $0. cancel ( id: id, path: navigationIDPath)
179
+ }
180
+ let task = Task { try await operation ( ) }
181
+ let cancellable = AnyCancellable { task. cancel ( ) }
182
+ $0. insert ( cancellable, at: id, path: navigationIDPath)
183
+ return ( cancellable, task)
177
184
}
178
- let task = Task { try await operation ( ) }
179
- let cancellable = AnyCancellable { task. cancel ( ) }
180
- _cancellationCancellables. insert ( cancellable, at: id, path: navigationIDPath)
181
- return ( cancellable, task)
182
- }
183
185
defer {
184
- _cancellablesLock . sync {
185
- _cancellationCancellables . remove ( cancellable, at: id, path: navigationIDPath)
186
+ _cancellationCancellables . withValue {
187
+ $0 . remove ( cancellable, at: id, path: navigationIDPath)
186
188
}
187
189
}
188
190
do {
@@ -193,25 +195,26 @@ extension Effect {
193
195
}
194
196
#else
195
197
@_unsafeInheritExecutor
196
- public func withTaskCancellation< ID : Hashable , T: Sendable > (
197
- id: ID ,
198
+ public func withTaskCancellation< T: Sendable > (
199
+ id: some Hashable ,
198
200
cancelInFlight: Bool = false ,
199
201
operation: @Sendable @escaping ( ) async throws -> T
200
202
) async rethrows -> T {
201
203
@Dependency ( \. navigationIDPath) var navigationIDPath
202
204
203
- let ( cancellable, task) = _cancellablesLock. sync { ( ) -> ( AnyCancellable , Task < T , Error > ) in
204
- if cancelInFlight {
205
- _cancellationCancellables. cancel ( id: id, path: navigationIDPath)
205
+ let ( cancellable, task) : ( AnyCancellable , Task < T , Error > ) = _cancellationCancellables
206
+ . withValue {
207
+ if cancelInFlight {
208
+ $0. cancel ( id: id, path: navigationIDPath)
209
+ }
210
+ let task = Task { try await operation ( ) }
211
+ let cancellable = AnyCancellable { task. cancel ( ) }
212
+ $0. insert ( cancellable, at: id, path: navigationIDPath)
213
+ return ( cancellable, task)
206
214
}
207
- let task = Task { try await operation ( ) }
208
- let cancellable = AnyCancellable { task. cancel ( ) }
209
- _cancellationCancellables. insert ( cancellable, at: id, path: navigationIDPath)
210
- return ( cancellable, task)
211
- }
212
215
defer {
213
- _cancellablesLock . sync {
214
- _cancellationCancellables . remove ( cancellable, at: id, path: navigationIDPath)
216
+ _cancellationCancellables . withValue {
217
+ $0 . remove ( cancellable, at: id, path: navigationIDPath)
215
218
}
216
219
}
217
220
do {
@@ -226,11 +229,11 @@ extension Task<Never, Never> {
226
229
/// Cancel any currently in-flight operation with the given identifier.
227
230
///
228
231
/// - Parameter id: An identifier.
229
- public static func cancel< ID : Hashable > ( id: ID ) {
232
+ public static func cancel( id: some Hashable & Sendable ) {
230
233
@Dependency ( \. navigationIDPath) var navigationIDPath
231
234
232
- return _cancellablesLock . sync {
233
- _cancellationCancellables . cancel ( id: id, path: navigationIDPath)
235
+ return _cancellationCancellables . withValue {
236
+ $0 . cancel ( id: id, path: navigationIDPath)
234
237
}
235
238
}
236
239
}
@@ -240,15 +243,14 @@ extension Task<Never, Never> {
240
243
let id : AnyHashable
241
244
let navigationIDPath : NavigationIDPath
242
245
243
- init < ID : Hashable > ( id: ID , navigationIDPath: NavigationIDPath ) {
246
+ init ( id: some Hashable , navigationIDPath: NavigationIDPath ) {
244
247
self . discriminator = ObjectIdentifier ( type ( of: id) )
245
248
self . id = id
246
249
self . navigationIDPath = navigationIDPath
247
250
}
248
251
}
249
252
250
- @_spi ( Internals) public var _cancellationCancellables = CancellablesCollection ( )
251
- private let _cancellablesLock = NSRecursiveLock ( )
253
+ @_spi ( Internals) public let _cancellationCancellables = LockIsolated ( CancellablesCollection ( ) )
252
254
253
255
@rethrows
254
256
private protocol _ErrorMechanism {
@@ -273,9 +275,9 @@ extension Result: _ErrorMechanism {}
273
275
public class CancellablesCollection {
274
276
var storage : [ _CancelID : Set < AnyCancellable > ] = [ : ]
275
277
276
- func insert< ID : Hashable > (
278
+ func insert(
277
279
_ cancellable: AnyCancellable ,
278
- at id: ID ,
280
+ at id: some Hashable ,
279
281
path: NavigationIDPath
280
282
) {
281
283
for navigationIDPath in path. prefixes {
@@ -284,9 +286,9 @@ public class CancellablesCollection {
284
286
}
285
287
}
286
288
287
- func remove< ID : Hashable > (
289
+ func remove(
288
290
_ cancellable: AnyCancellable ,
289
- at id: ID ,
291
+ at id: some Hashable ,
290
292
path: NavigationIDPath
291
293
) {
292
294
for navigationIDPath in path. prefixes {
@@ -298,17 +300,17 @@ public class CancellablesCollection {
298
300
}
299
301
}
300
302
301
- func cancel< ID : Hashable > (
302
- id: ID ,
303
+ func cancel(
304
+ id: some Hashable ,
303
305
path: NavigationIDPath
304
306
) {
305
307
let cancelID = _CancelID ( id: id, navigationIDPath: path)
306
308
self . storage [ cancelID] ? . forEach { $0. cancel ( ) }
307
309
self . storage [ cancelID] = nil
308
310
}
309
311
310
- func exists< ID : Hashable > (
311
- at id: ID ,
312
+ func exists(
313
+ at id: some Hashable ,
312
314
path: NavigationIDPath
313
315
) -> Bool {
314
316
self . storage [ _CancelID ( id: id, navigationIDPath: path) ] != nil
0 commit comments