简体   繁体   中英

DispatchGroup with async functions in a Firebase Realtime DB observe method in Swift

I'm developing an iOS chat app that uses Firebase Realtime Database for storing messages. I have a function that is called when the home chat screen is loaded. This function loads the recipient name, last message, timestamp and a profile pic. I've used DispatchGroup to sync all the calls. At first, I thought that it worked, but when I send a new message (update the DB in any way) the app crashes. I believe it is because the observe closure is being called again, and there is an imbalance between the enter/leave calls. I can't think of a way to make it work with DispatchGroup. Is there a way to fix this? Or is there a better option than DispatchGroup?

This is the main function with the firebase observer:

func getAllChatsForCurrentUser(completion: @escaping (_ chats: [Chat], _ error: Error?) -> Void) {
        var chats = [Chat]()
        let group = DispatchGroup()
        let currentUserUID = Auth.auth().currentUser!.uid
        let chatRef = Database.database().reference(withPath: "chats")
        group.enter()
        chatRef.observe(.value) { (snapshot) in
            var childrenArray = [String]()
            let children = snapshot.children
            while let rest = children.nextObject() as? DataSnapshot {
                childrenArray.append(rest.key)                         //1
            }
            for child in childrenArray {
                if child.contains(currentUserUID) {                    //2
                    let otherUserUID = child.replacingOccurrences(of: currentUserUID, with: "")
                    group.enter()
                    self.getChatInfo(uid: otherUserUID, chatID: child) { (chat, err) in
                        chats.append(chat)
                        group.leave()
                    }
                }
            }
            group.leave()
        }
        group.notify(queue: .main) {
            completion(chats, nil)
        }

    }

1 - For the chat name I use a combination of 2 uid's. So here I have an array of all chats.

2 - If the chat name contains the current users uid - I'm working with it. The recipients uid in the other part of the string.

getChatInfo function below:

func getChatInfo(uid: String, chatID: String, completion: @escaping (_ chat: Chat, _ error: Error?) -> Void) {
        let miniGroup = DispatchGroup()
        var newChat = Chat()
        newChat.otherUserUid = uid
        miniGroup.enter()
        self.getUserProfileFromUID(uid: uid) { (user, error) in
            newChat.name = user.name
            newChat.profilePic = user.photoURL
            miniGroup.leave()
        }
        miniGroup.enter()
        self.getLastMessageAndTimeForChat(chatID: chatID) { (message, time, error) in
            newChat.lastMessage = message
            newChat.lastMessageTime = time
            miniGroup.leave()
        }
        miniGroup.notify(queue: .main) {
            completion(newChat, nil)
        }
    }

I know that this is probably a bad way of structuring the data and calling the functions. At least I've been told so, without reasoning. Been stuck with this problem for nearly a week now, any info would be greatly appreciated.

UPDATE 1 Tried wrapping the leave() calls in defer {} , and tried playing around with NSOperations instead of DispatchGroup. Still no luck.

添加我的数据库的屏幕截图以供参考。

So I figured it out by using a completion handler with a begin handler.

getChatsWithBeginAndComplete(beginHandler: {
            self.group.enter()
            self.group.notify(queue: .main) {
                print("done")
                self.tableView.reloadData()
            }
        }) {
            self.group.leave()
        }

And the function:

func getChatsWithBeginAndComplete(beginHandler: @escaping () -> (), completionHandler: @escaping () -> ()) {
       allChatsHandle = allChatsRef.observe(.value) { (snapshot) in
           let bigGroup = DispatchGroup()
           beginHandler()
           var childrenArray = [String]()
           let children = snapshot.children
           while let rest = children.nextObject() as? DataSnapshot {
               childrenArray.append(rest.key)
           }
           for chatID in childrenArray {
               if chatID.contains(currentUserUID) {
                   bigGroup.enter()
                   let funcGroup = DispatchGroup()

                   //Do more async stuff in the funcGroup

                   funcGroup.notify(queue: .main) {
                       self.chats.append(chat)
                       bigGroup.leave()
                   }
               }
           }

           bigGroup.notify(queue: .main) {
               completionHandler()
           }

       }
   }

So here all the group.enter and group.leave calls are balanced, because they are called from the completion/begin handlers, or from inside the firebase observer. I don't think that it's the best way to handle this problem, but it's definitely one way. If somebody knows a better solution - please let me know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM