При запуске следующего кода моя функция возвращает пустой массив для функции fetchProfilesForList
.
Это связано с тем, что функция не ожидает завершения выборки профилей перед возвратом.
Как изменить следующий код, чтобы дождаться ответа асинхронной функции?
Модель представления
func addNoteToList(title: String, note: String) {
if let id = self.listID {
if let userID = profileID {
fm.fetchProfile(uid: userID, completion: { [self] profile in
print("FETCHED USER PROFILE FOR SHORTCUT")
fm.fetchList(uid: id, completion: { [self] list in
print("FETCHED LIST FOR SHORTCUT")
fetchProfilesForList(list: List, completion: { profiles in
print("users retuned \(profiles.count)")
for p in profiles {
print("ADD NOTE TO LIST SHORTCUT")
//Create LIST
let note = Payment(
title: title, note: note, user: p.uid )
//Add payment to home
var l = list
l.notes.append(note)
self.fm.updateList(l)
}
})
})
})
}
}
}
func fetchProfilesForList(list: List, completion: @escaping ([Profile]) -> Void) {
var group: [Profile] = []
if list.admin != profileID && list.group.contains(profileID!) {
print("IS NOT ADMIN and add to note")
fm.fetchProfile(uid: home.admin, completion: { admin in
print("FETCHED ADMIN")
people.append(admin)
})
}
for p in list.group {
print("P IN GROUP")
if p != profileID! {
// Fetch profile
fm.fetchProfile(uid: p, completion: { profile in
print("profile \(profile.firstName)")
group.append(profile)
})
}
}
let users = group.removeDuplicates() // incase fetched twice
completion(p) // RETURNS BEFORE PROFILES ARE FETCHED
}
Менеджер пожарного магазина
public class FirestoreManager {
let db = Firestore.firestore()
func fetchList(uid: String, completion: @escaping (Home) -> Void) {
db.collection("lists").document(uid).getDocument { (document, error) in
if let document = document, document.exists {
do {
let l = try document.data(as: List.self)
completion(l)
}
catch {
print("There was an error getting decoding List")
}
} else {
print("Document for List
\(uid) does not exist")
}
}
}
func fetchProfile(uid: String, completion: @escaping (Profile) -> Void) {
db.collection("users").document(uid).getDocument { (document, error) in
if let document = document, document.exists {
do {
let p = try document.data(as: Profile.self)
completion(p)
}
catch {
print("There was an error getting decoding Profile")
}
} else {
print("Document for users / profile does not exist")
}
}
}
Это происходит потому, что выполняется несколько вызовов асинхронной выборки. Поскольку это асинхронные вызовы, и завершение основной функции вызывается до того, как запросы выборки вернут данные обратно.
Есть два способа решить эту проблему -
Использование DispatchGroup –
func fetchProfilesForList(list: List, completion: @escaping ([Profile]) -> Void) {
var group: [Profile] = []
let dispatchGroup = DispatchGroup()
if list.admin != profileID && list.group.contains(profileID!) {
print("IS NOT ADMIN and add to note")
// Enters in dispatch group
dispatchGroup.enter()
fm.fetchProfile(
uid: home.admin,
completion: {
admin in
print("FETCHED ADMIN")
group.append(admin) // Assuming, you are appending to "group here instead of "people"
// Leaves the group
dispatchGroup.leave()
}
)
}
for p in list.group {
print("P IN GROUP")
if p != profileID! {
// Fetch profile
dispatchGroup.enter()
fm.fetchProfile(
uid: p,
completion: {
profile in
print("profile \(profile.firstName)")
group.append(profile)
dispatchGroup.leave()
}
)
}
}
// It gets called only when all the calls leave DispatchGroup
// So it will wait for all the async calls to complete
dispatchGroup.notify(queue: .main) {
let users = group.removeDuplicates() // incase fetched twice
completion(users) // Assuming you want to return "users" here instead of "p"
}
}
Использование async/await – Айнтаксис async/await естественен и намного чище, чем обратные вызовы. Если вы хотите использовать async/await, вам придется преобразовать функции обратного вызова в асинхронные функции, используя в этом случае либо withContinuation, либо withCheckedContinuation.
Вы можете преобразовать свои функции обратного вызова в асинхронные следующим образом:
func fetchProfile(uid: String) async -> Profile? {
await withCheckedContinuation { continuation in
db.collection("users").document(uid).getDocument { (document, error) in
if let document = document, document.exists {
do {
let profile = try document.data(as: Profile.self)
continuation.resume(returning: profile)
} catch {
print("There was an error decoding Profile")
continuation.resume(returning: nil)
}
} else {
print("Document for users / profile does not exist")
continuation.resume(returning: nil)
}
}
}
}
и использовать их вот так
func fetchProfilesForList(list: List) async -> [Profile] {
var group: [Profile] = []
if list.admin != profileID && list.group.contains(profileID!) {
print("IS NOT ADMIN and add to note")
if let admin = await fetchProfile(uid: list.admin) {
print("FETCHED ADMIN")
group.append(admin)
}
}
for p in list.group {
print("P IN GROUP")
if p != profileID!, let profile = await fetchProfile(uid: p) {
print("profile \(profile.firstName)")
group.append(profile)
}
}
let users = group.removeDuplicates() // In case fetched twice
return users
}