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
(just an example) within it.
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 {
// Just an example for a higher-order async func
func forEachAsync(_ operation: (Element) async -> Void) async {
for element in self {
await operation(element)
}
}
}
Enable Build Settings -> Approachable Concurrency. This is enabled by default for new projects. Thank you, Apple.
Or enable only Build Settings -> nonisolated(nonsending) By Default. Approachable Concurrency enables this and others.
Or, if you are worried about potentially changing the behavior of existing code, there's an explicit variant:
extension Sequence {
nonisolated(nonsending) func forEachAsync(
_ operation: nonisolated(nonsending) (Element) async -> Void
) async {
for element in self {
await operation(element)
}
}
}
Swift packages:
Use swift-tools-version 6.2 and add the NonisolatedNonsendingByDefault
upcoming feature flag:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
...
targets: [
.target(
...
swiftSettings: [
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("InferIsolatedConformances") // Optional
]
)
]
)
Sources:
With Swift Language Version set to Swift 6 in the Build Settings:
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)
}
}
}
You can still use forEachAsync
outside of an Actor
, in which case isolation
is nil.
Sources: