Как дождаться ответа асинхронной функции в Swift

При запуске следующего кода моя функция возвращает пустой массив для функции 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")
        }
     }
}

55
1

Ответ:

Решено

Это происходит потому, что выполняется несколько вызовов асинхронной выборки. Поскольку это асинхронные вызовы, и завершение основной функции вызывается до того, как запросы выборки вернут данные обратно.

Есть два способа решить эту проблему -

  1. Использование DispatchGroup для ожидания завершения всех асинхронных вызовов перед вызовом завершения основной функции.
  2. Используйте async/await, если ваш проект может поддерживать минимальную необходимую для этого версию.

Использование 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
}