Integrating external cancellation tokens + Swift concurrency cancellation #1400
-
Sometimes dependencies provide their own cancellation tokens. A simple example I happen to be interested in at the moment is the class function Say I have some host view, in which the user may click a button the present the Safari view controller. My thought is to fire this prewarming effect from an action sent in the I took a shot at writing such a dependency, but it's an odd function that returns Has anyone encountered such a pattern when building with concurrency TCA? Is it a weird code smell to write never-returning async functions that exist only to suspend and be cancelled? Have I overlooked some way simpler way to do this? (Often the case.) Thanks in advance for any insight! import ComposableArchitecture
import Dependencies
import SFSafariServices
extension DependencyValues {
public var prewarmConnections: @Sendable ([URL]) async throws -> Never {
get { self[PrewarmConnectionsKey.self] }
set { self[PrewarmConnectionsKey.self] = newValue }
}
public enum PrewarmConnectionsKey: TestDependencyKey {
public static let testValue: @Sendable ([URL]) async throws -> Never = { _ in
try await Task.never()
}
}
}
extension DependencyValues.PrewarmConnectionsKey: DependencyKey {
public static let liveValue: @Sendable ([URL]) async throws -> Never = { urls in
let prewarmingToken = await MainActor.run {
let prewarmingToken = SFSafariViewController.prewarmConnections(to: urls)
return UncheckedSendable(prewarmingToken)
}
return try await withTaskCancellationHandler {
try await Task.never()
} onCancel: {
prewarmingToken.wrappedValue.invalidate()
}
}
} (A possibly related or not issue is that I am making an assumption about the sendability of the reference-type cancellation token return from SFSafariServices, but whether or not that is valid, my larger question about wrapping cancellation tokens stands.) |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
@kamcma We definitely don't think it's a code smell to use It's a great way to suspend for as long as you need to, and terminate when some outside event occurs (via cancellation). In this particular example I'd probably need to think more about alternatives, but it seems clever to me on first look! |
Beta Was this translation helpful? Give feedback.
-
If anyone comes across this post later, it has since occurred to me that making making my dependency endpoint return an extension DependencyValues.PrewarmConnectionsKey: DependencyKey {
public static let liveValue: @Sendable ([URL]) async -> AsyncStream<Never> = { urls in
let prewarmingToken = await SFSafariViewController.prewarmConnections(to: urls)
return AsyncStream { continuation in
continuation.onTermination = { _ in
prewarmingToken.invalidate()
}
}
}
} |
Beta Was this translation helpful? Give feedback.
@kamcma We definitely don't think it's a code smell to use
Task.never()
in this kind of way 😄 In fact we've used it in similar ways to great effect, some of which may make it into future TCA features.It's a great way to suspend for as long as you need to, and terminate when some outside event occurs (via cancellation).
In this particular example I'd probably need to think more about alternatives, but it seems clever to me on first look!