Reputation: 9040
I'm trying to resolve a compilation error: Task or actor isolated value cannot be sent
when using Xcode 16.0 beta 3 with Swift 6 and Complete
Concurrency enabled. My code:
import SwiftUI
@preconcurrency import Contacts
import ContactsUI
struct SimpleContactPicker: View {
let allowedContactType: CNContactType? = CNContactType.person
@Binding var selectedContacts: [CNContact]?
var body: some View {
SimpleContactPickerProxy(allowedContactType: self.allowedContactType, selectedContacts: $selectedContacts)
.ignoresSafeArea()
}
}
extension CNContactType {
var predicateForEnablingContact: NSPredicate {
NSPredicate(format: "contactType=\(self.rawValue)")
}
}
struct SimpleContactPickerProxy: UIViewControllerRepresentable, @unchecked Sendable {
typealias UIViewControllerType = CNContactPickerViewController
let allowedContactType: CNContactType?
@Binding var selectedContacts: [CNContact]?
final class Coordinator: NSObject, CNContactPickerDelegate, @unchecked Sendable {
var parent: SimpleContactPickerProxy
init(_ parent: SimpleContactPickerProxy) {
self.parent = parent
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
Task { @MainActor in
self.parent.selectedContacts = contacts // <<-- ERROR: Task or actor isolated value cannot be sent
}
}
}
func makeUIViewController(context: Context) -> CNContactPickerViewController {
let contactPickerViewController = CNContactPickerViewController()
contactPickerViewController.delegate = context.coordinator
if let allowedContactType {
contactPickerViewController.predicateForEnablingContact = allowedContactType.predicateForEnablingContact
}
return contactPickerViewController
}
func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
While I generally understand that the [CNContact]
array cannot be sent from the non-isolated context to the MainActor
context because CNContact
is non-Sendable, it's unclear how to work around this problem. (This issue did not appear in earlier Xcode 16 betas).
I guess it's possible to create my own CNContact
facsimile struct with copied data, but this seems like an annoying amount of overhead. I also tried MainActor.assumeIsolated
and other strategies discussed in this WWDC 2024 Swift Concurrency video https://developer.apple.com/wwdc24/10169, but none of these approaches appeared to work.
Any help is appreciated.
Upvotes: 2
Views: 3532
Reputation: 29614
The first thing you should know what what types can be declared as Sendable
safely.
Value types
Reference types with no mutable storage
Reference types that internally manage access to their state
Functions and closures (by marking them with @Sendable)
marking CNContact
as Sendable
is ok
A CNContact object stores an immutable copy of a contact’s information, so you cannot change the information in this object directly. Contact objects are thread-safe, so you may access them from any thread of your app.
Note that View
and Coordinator
can be made safe with @MainActor
they don't require Sendable
.
import SwiftUI
import Contacts
import ContactsUI
struct ContactsView: View {
@State private var selected: [CNContact] = []
var body: some View {
SimpleContactPicker(selectedContacts: $selected)
}
}
#Preview {
ContactsView()
}
struct SimpleContactPicker: View {
let allowedContactType: CNContactType? = CNContactType.person
@Binding var selectedContacts: [CNContact]
var body: some View {
SimpleContactPickerProxy(allowedContactType: self.allowedContactType, selectedContacts: $selectedContacts)
.ignoresSafeArea()
}
}
struct SimpleContactPickerProxy: UIViewControllerRepresentable {
typealias UIViewControllerType = CNContactPickerViewController
let allowedContactType: CNContactType?
@Binding var selectedContacts: [CNContact]
@MainActor
final class Coordinator: NSObject, CNContactPickerDelegate {
var parent: SimpleContactPickerProxy
init(_ parent: SimpleContactPickerProxy) {
self.parent = parent
}
nonisolated func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
}
nonisolated func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
Task { @MainActor in
parent.selectedContacts = contacts
}
}
}
func makeUIViewController(context: Context) -> CNContactPickerViewController {
let contactPickerViewController = CNContactPickerViewController()
contactPickerViewController.delegate = context.coordinator
if let allowedContactType {
contactPickerViewController.predicateForEnablingContact = allowedContactType.predicateForEnablingContact
}
return contactPickerViewController
}
func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
extension CNContactType {
var predicateForEnablingContact: NSPredicate {
NSPredicate(format: "contactType=\(self.rawValue)")
}
}
///Immutable & thread-safe
extension CNContact: @unchecked @retroactive Sendable {}
Also note that
final class Coordinator: NSObject, CNContactPickerDelegate, @unchecked Sendable
is incorrect because it does not comply with any of the accepted forms of Sendable
it is a reference type with mutable storage.
Upvotes: 1