How to adjust forEachAsync
for use within TestActor
without a warning? It should stay reusable in other places.
I need TestActor
to pass around non-sendable stuff within it. And I want to use higher-order convenience functions like forEachAsync
within it. Execution is sequential on purpose.
Build settings -> Strict Concurrency Checking: Complete
class NotSendable { }
actor TestActor {
func foo() async {
let nonSendables = [NotSendable(), NotSendable()]
// No problem
for notSendable in nonSendables {
await bar(notSendable)
}
// Warning: Sending 'nonSendables' risks causing data races
await nonSendables.forEachAsync{ notSendable in
await bar(notSendable)
}
}
private func bar(_: NotSendable) async { }
}
extension Sequence {
func forEachAsync(_ operation: (Element) async -> Void) async {
for element in self {
await operation(element)
}
}
}
Solution:
class NotSendable { }
actor TestActor {
func foo() async {
let nonSendables = [NotSendable(), NotSendable()]
await nonSendables.forEachAsync{ notSendable, isolation in
await bar(notSendable, isolation)
}
}
private func bar(
_: NotSendable,
_ isolation: isolated (any Actor)? = #isolation
) async {
}
}
extension Sequence {
func forEachAsync(
_ operation: (Element, isolated (any Actor)?) async -> Void,
isolation: isolated (any Actor)? = #isolation
) async {
for element in self {
await operation(element, isolation)
}
}
}
It's ugly but it works in Xcode 16.2 with Swift Language Version set to Swift 6 in the Build Settings. You can still use forEachAsync
outside of an Actor
, in which case isolation
is nil.
Sources:
SE-0313 – Improved control over actor isolation
SE-0420 – Inheritance of actor isolation
Hopefully this proposal will be accepted and make things easier and prettier:
SE-0461 – Run nonisolated async functions on the caller's actor by default