You are using a Swift library that wasn't correctly built for Swift concurrency and things are going wrong.
You need to pass a type from this library to a @Sendable
closure. Just remember, nothing will make this magically safe and you still should be confident you are not introducing data races.
import TheLibrary
func useTheType() {
let value = TypeFromTheLibrary()
Task {
value.doStuff() // WARNING: Capture of 'value' with non-sendable...
}
}
This is an easy one. You can just import the library with @preconcurrency
.
@preconcurrency import TheLibrary
func useTheType() {
let value = TypeFromTheLibrary()
Task {
value.doStuff()
}
}
This addresses the issue because it forces all accesses to be isolated to a single actor.
import TheLibrary
@MainActor
func useTheType() {
let value = TypeFromTheLibrary()
Task {
value.doStuff()
}
}
This is a more-flexible version of #2. Note that is doesn't work as of Swift 5.10, but hopefully will soon!.
import TheLibrary
func useTheType(isolatedTo actor: any Actor) {
let value = TypeFromTheLibrary()
Task {
value.doStuff()
}
}
You need to use a type from this library to initialize a static variable.
import TheLibrary
class YourClass {
static let value = TypeFromTheLibrary() // WARNING: Static property 'value' is not concurrency-safe...
}
This construct was introduced specifically to handle this situation. It's worth noting that @preconcurrency import
does affect this behavior: it will suppress any errors related to isolation checking by turning them into warnings.
import TheLibrary
class YourClass {
nonisolated(unsafe) static let value = TypeFromTheLibrary()
}
You have a protocol that uses callbacks. These callbacks are not correctly marked with global actors or @Sendable
.
import TheLibrary
class YourClass: LibraryProtocol {
func protocolFunction(callback: @escaping () -> Void)
Task {
// doing your async work here
// WARNING: Capture of 'callback' with non-sendable type '() -> Void' in a `@Sendable` closure
callback()
}
}
If you import the library with @preconcurrency
, you can adjust your conformance to match the @Sendable
reality of the function.
@preconcurrency import TheLibrary
class YourClass: LibraryProtocol {
// the callback is documented to actually be ok to call on any thread, so it must be @Sendable. With preconcurrency, this Sendable mismatch is ok.
func protocolFunction(callback: @escaping @Sendable () -> Void)
Task {
// doing your async work here
callback()
}
}
Almost the same as #1, but the callback must be run on the main actor. In this case, it is not possible to add @MainActor
to the conformance, and you have to instead make the isolation manual.
@preconcurrency import TheLibrary
class YourClass: LibraryProtocol {
// the callback is documented to actually be ok to call on any thread, so it must be @Sendable. With preconcurrency, this mismatch is still considered a match.
func protocolFunction(callback: @escaping @Sendable () -> Void)
Task {
// doing your async work here
// ensure you are back on the MainActor here
await MainActor.run {
callback()
}
}
}
You want to convert a function that uses a callback to async. This is particularly common when interoperating with Objective-C code, and the compiler will even generate async versions of certain patterns automically. But isolation and Sendability can still be a problem.
func doWork(argument: ArgValue, _ completionHandler: @escaping (ResultValue) -> Void)
If the arguments (ArgValue
) and return value (ResultValue
) are Sendable
, this is very straightforward. Remember, though, that just putting async on a function without any isolation makes it non-isolated. You must make sure that's compatible with how the function you are wrapping works.
func doWork() async -> ResultValue {
await withCheckedContinuation { continuation in
doWork { value in
continuation.resume(returning: value)
}
}
}
A non-Sendable return value can potentially be a showstopper. But, if you do not need the entire object, or can transform it in some way before returning it, you can pull this off.
func doWork() async -> ResultValue {
await withCheckedContinuation { continuation in
doWork { nonSendableValue in
// within this block, it's safe to synchronously access the non-Sendable value
let sendableThing = nonSendableValue.partYouReallyNeed
continuation.resume(returning: sendableThing)
}
}
}