Thanks for this question. To give you a clue on how I (think!) I figured this out, I had to use your information to reconstruct the types involved. The types are all that matter here!
import Foundation
// a non-Sendable type
class StoreListProduct: NSObject {
}
final class AppStoreProduct: NSObject, Sendable {
class func product(forListProduct: StoreListProduct) async -> AppStoreProduct? {
// the body here does not matter, just the signature
return nil
}
}
class Container {
@objc class func fetch(product: StoreListProduct, completion: @escaping @Sendable (AppStoreProduct?) -> Void) {
// I'm not sure what the isolation here is, but I'm assuming nothing
Task {
// the compiler is telling us that it cannot pass this closure out to Task, because it cannot send it.
// I believe the reason *why* is becuase we've captured product, but it doesn't know how/when product is
// used by the caller and it is not Sendable, making a transfer unsafe.
let result = await AppStoreProduct.product(forListProduct: product)
// and here are moving result into the MainActor
DispatchQueue.main.async {
completion(result)
}
}
}
}
So, what's a solution? We have to somehow allow that product variable to transfer over to that Task. You have some options.
option 1: explicitly allow the transfer (Swift 6-only)
By using sending, we are telling the compiler that all call-sites must allow a transfer. This fixes the error, but adds extra constraints on the call-sites that may or may not be ok.
@objc class func fetch(
product: sending StoreListProduct,
completion: @escaping @Sendable (AppStoreProduct?) -> Void
)
option 2: mark everything MainActor
This is Swift 5-compatible, and probably a reasonable solution. Plus, by doing this, you can avoid an additional hop and add more explicitness to your API.
class Container {
@MainActor
@objc class func fetch(
product: StoreListProduct,
// unforutnately, in Swift 5, this closure must *also* be marked @Sendable
completion: @escaping @MainActor (AppStoreProduct?) -> Void
) {
Task {
let result = await AppStoreProduct.product(forListProduct: product)
completion(result)
}
}
}