Я хочу получить контакты пользователя, используя методы enumerateContacts(with:usingBlock:)
и async/await
. Вот моя функция:
func fetchContacts() async throws -> [Contact] {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
let store = CNContactStore()
let contactsActor = ContactsActor()
var contactsArray: [Contact] = []
return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global(qos: .background).async {
do{
try store.enumerateContacts(with: request) { contact, stop in
let contact = Contact(
givenName: contact.givenName,
familyName: contact.familyName,
emails: contact.emailAddresses.map { $0.value as String }
)
Task {
await contactsActor.appendToContacts(contact: contact)
}
}
continuation.resume(returning: contactsArray )
} catch {
continuation.resume(throwing: error)
}
}
}
}
Также я использую этого актера:
actor ContactsActor {
var contacts:[Contact] = []
func appendToContacts(contact: Contact) {
contacts.append(contact)
}
func getContact()-> [Contact]{
return contacts
}
}
В методе viewDidLoad
я вызываю функцию fetchContacts
внутри задачи:
Task {
let contacts = try await fetchContacts()
await MainActor.run(body: {
self.contactsTable.reloadData()
})
}
Перед continuation.resume(returning: contactsArray )
я получаю эту ошибку:
Ссылка на захваченную переменную 'contactsArray' в одновременно исполняемом коде.
Я изучаю параллелизм Swift и точно не знаю, как решить эту ошибку.
Я ожидал получить контакты пользователя в массиве специальной структуры с именем Contact
.
Я бы избегал API GCD и вместо этого следовал шаблонам параллелизма Swift (например, отдельной задаче):
func fetchContacts() async throws -> [Contact] {
let task = Task.detached {
let keys = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey as CNKeyDescriptor
]
let request = CNContactFetchRequest(keysToFetch: keys)
let store = CNContactStore()
let formatter = CNContactFormatter()
formatter.style = .fullName
var contacts: [Contact] = []
try store.enumerateContacts(with: request) { contact, stop in
guard !Task.isCancelled else {
stop.pointee = true
return
}
let contact = Contact(
givenName: contact.givenName,
familyName: contact.familyName,
fullName: formatter.string(from: contact),
emails: contact.emailAddresses.map { $0.value as String }
)
contacts.append(contact)
}
try Task.checkCancellation()
return contacts
}
return try await withTaskCancellationHandler {
try await task.value
} onCancel: {
task.cancel()
}
}
Вышеупомянутое я также поддерживаю отмену
Task.isCancelled
; иCancellationError
с Task.checkCancellation()
.Кроме того, если вы меня простите, я ввел дополнительный параметр для полного имени в ваш тип Contact
и использовал CNContactFormatter
для построения этой строки. Это вопрос личных предпочтений, поэтому делайте в этом отношении все, что хотите. Это не имеет отношения к более широкому вопросу.
Все хорошо, но здесь есть небольшая административная проблема; этот ОП, вместо того, чтобы ждать повторного открытия stackoverflow.com/review/reopen/36309704 (что почти и есть), поторопился и повторил вопрос. Это неправильное поведение.
Я не могу его полностью винить. Столько вопросов, закрытых по тем или иным причинам, никогда не открываются вновь. И прошла почти неделя, так сколько ему ждать? Возможно, ему следует просто удалить этот вопрос на этом этапе…
Согласитесь, это было бы здорово.
Огромное спасибо за помощь @Rob. Я удалил первый.
Кстати,
Task {…}
вviewDidLoad
уже есть на главном актере (посколькуviewDidLoad
изолирован от главного актера в результате изоляцииUIViewController
от главного актера), поэтомуMainActor.run {…}
не нужен.