1

i'm try to run my function in order to to download some realtime database data change from firebase cloud firestore.

the problem is the my closure onSuccess fire to early, and the array in the closure is always empty.

how can I handle the order of execution in order to execute the closure on success only after the forEach()

after reading online I come up with some queue , but i'm not sure if i'm using correctly.

below my code:

func getuserConfirmFriend(userLoggato: UserModel, onSuccess: @escaping([UserModel]) -> Void, onError: @escaping(_ errorMessage: String) -> Void, newPendingUser: @escaping(UserModel) -> Void, userRemoved: @escaping(UserModel) -> Void , listener: @escaping(_ listenerHandle: ListenerRegistration) -> Void){
        let queue = OperationQueue()
        let listenerRegistration = db.collection("user").document(userLoggato.userID).collection("confirmFriend").addSnapshotListener(includeMetadataChanges: true) { documentSnapshot, error in
            var userConfirmFriendsArray = [UserModel]()
            guard let snapshot = documentSnapshot else { return }
            
            let operation1 = BlockOperation {
                
                snapshot.documentChanges.forEach { (documentChange) in
                    switch documentChange.type {
                    case .added :
                        let dict = documentChange.document.data()
                        let name = dict["name"] as? String ?? "na name"
                        let surname = dict["surname"] as? String ?? "na name"
                        let email = dict["email"] as? String ?? "na name"
                        let userLevel = dict["adminLevel"] as? String ?? "unable to get admin level"
                        let idUser = dict["userID"] as? String ?? "no ID"
                        let position1 = dict["position"] as? String ?? "na preferance position"
                        let position2 = dict["position2"] as? String ?? "na preferance position"
                        let vote = dict["vote"] as? Int ?? 0
                        self.downloadImageForAdmin(userID: idUser) { (urlImage) in
                            let utente = UserModel(name: name, surname: surname, email: email, userID: idUser, adminLevel: userLevel, immagine: urlImage, position: position1, position2: position2, vote: vote)
                            
                            newPendingUser(utente)
                            userConfirmFriendsArray.append(utente)
                            //                           onSuccess(userConfirmFriendsArray)
                        }
                        print("CONFIRM User Added")
                    case .modified :
                        //implements action (new escaping)
                        
                        print("CONFIRM User Modified ")
                    case .removed :
                        print("CONFIRM User Removed")
                        let dict = documentChange.document.data()
                        let name = dict["name"] as? String ?? "na name"
                        let surname = dict["surname"] as? String ?? "na name"
                        let email = dict["email"] as? String ?? "na name"
                        let userLevel = dict["adminLevel"] as? String ?? "unable to get admin level"
                        let idUser = dict["userID"] as? String ?? "no ID"
                        let position1 = dict["position"] as? String ?? "na preferance position"
                        let position2 = dict["position2"] as? String ?? "na preferance position"
                        let vote = dict["vote"] as? Int ?? 0
                        self.downloadImageForAdmin(userID: idUser) { (urlImage) in
                            let utente = UserModel(name: name, surname: surname, email: email, userID: idUser, adminLevel: userLevel, immagine: urlImage, position: position1, position2: position2, vote: vote)
                            userRemoved(utente)
                        }
                    }
                    
                    
                    
                }
            }
            let operation2 = BlockOperation{
                DispatchQueue.main.async {
                    
                onSuccess(userConfirmFriendsArray)
                }
            }
            
            queue.addOperation(operation1)
            queue.addOperation(operation2)
            

        }
              
           listener(listenerRegistration)
       }

my problem is userConfirmFriend is always empty, even if the code execute first operation1 and then 2

not so familiar with dispatch queue.

thanks

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
Damiano Miazzi
  • 1,514
  • 1
  • 16
  • 38
  • 2
    You need to _get_ familiar with dispatch queue. Your code is _asynchronous_. If you don't know what that means, find out; I've got some articles about it here: http://www.programmingios.net/what-asynchronous-means/ To complete all iterations of a loop before proceeding to call the completion handler, you need a _dispatch group_. This has been explained many many many times here on Stack Overflow already. (Did I mention "many"?) Please _search_ before asking yet again. Thanks. – matt Aug 02 '20 at 15:14
  • 1
    Does this answer your question? [Returning data from async call in Swift function](https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function) – Joakim Danielson Aug 02 '20 at 16:30

1 Answers1

0

Your issue is on the self.downloadImageForAdmin call within the forEach loop under switch case .added. As the downloading is async, the forEach loop operation would've typically completed before downloads have completed and leaving your array empty as you only append to the array on callback.

A way around this is to use DispatchGroup (Apple Documentation) (below code not tested, although should give you an idea on how to use DispatchGroup in this case).

func getuserConfirmFriend(userLoggato: UserModel, onSuccess: @escaping([UserModel]) -> Void, onError: @escaping(_ errorMessage: String) -> Void, newPendingUser: @escaping(UserModel) -> Void, userRemoved: @escaping(UserModel) -> Void , listener: @escaping(_ listenerHandle: ListenerRegistration) -> Void)
{
    let dispatchGroup = DispatchGroup()
    let listenerRegistration = db.collection("user").document(userLoggato.userID).collection("confirmFriend").addSnapshotListener(includeMetadataChanges: true) { documentSnapshot, error in
        var userConfirmFriendsArray = [UserModel]()
        guard let snapshot = documentSnapshot else { return }

            snapshot.documentChanges.forEach { (documentChange) in
            switch documentChange.type {
                case .added :
                    let dict = documentChange.document.data()
                    let name = dict["name"] as? String ?? "na name"
                    let surname = dict["surname"] as? String ?? "na name"
                    let email = dict["email"] as? String ?? "na name"
                    let userLevel = dict["adminLevel"] as? String ?? "unable to get admin level"
                    let idUser = dict["userID"] as? String ?? "no ID"
                    let position1 = dict["position"] as? String ?? "na preferance position"
                    let position2 = dict["position2"] as? String ?? "na preferance position"
                    let vote = dict["vote"] as? Int ?? 0
                    dispatchGroup.enter()
                    self.downloadImageForAdmin(userID: idUser) { (urlImage) in
                        let utente = UserModel(name: name, surname: surname, email: email, userID: idUser, adminLevel: userLevel, immagine: urlImage, position: position1, position2: position2, vote: vote)
                        
                        newPendingUser(utente)
                        userConfirmFriendsArray.append(utente)
                        dispatchGroup.leave()
                    }
                    print("CONFIRM User Added")
                case .modified :
                    //implements action (new escaping)

                    print("CONFIRM User Modified ")
                case .removed :
                    print("CONFIRM User Removed")
                    let dict = documentChange.document.data()
                    let name = dict["name"] as? String ?? "na name"
                    let surname = dict["surname"] as? String ?? "na name"
                    let email = dict["email"] as? String ?? "na name"
                    let userLevel = dict["adminLevel"] as? String ?? "unable to get admin level"
                    let idUser = dict["userID"] as? String ?? "no ID"
                    let position1 = dict["position"] as? String ?? "na preferance position"
                    let position2 = dict["position2"] as? String ?? "na preferance position"
                    let vote = dict["vote"] as? Int ?? 0
                    self.downloadImageForAdmin(userID: idUser) { (urlImage) in
                        let utente = UserModel(name: name, surname: surname, email: email, userID: idUser, adminLevel: userLevel, immagine: urlImage, position: position1, position2: position2, vote: vote)
                        userRemoved(utente)
                    }
            }
        }
        dispatchGroup.notify(queue: .main) {
            onSuccess(userConfirmFriendsArray)
        }
    }
    listener(listenerRegistration)
}

DispatchGroup enter() adds a block to the group, and leave() signals that block has completed. Once all blocks added to the DispatchGroup have completed, notify() is called.

Leon Storey
  • 3,274
  • 2
  • 25
  • 40