Ensuring Async Action Has Completed in Firebase

2019-07-09 04:40发布

I need to run concurrent queries to Firebase in swift. How can I ensure that a looped query has finished before another action is allowed to start in my app?

For example, the first query is a straightforward, and simply pulls data. but the second, iterates through an array looking for a certain node in my firebase database, looping through the array self.contacts:

 // First check if friend, and remove/add to
            friendsURL.observeSingleEventOfType(.Value, withBlock: { snapshot in

                for oneSnapshot in snapshot.children {

                    for oneContact in contactsArray {

                        for oneContactPhoneNum in oneContact.phoneNumbers {

                            let phoneNumber = oneContactPhoneNum.value as! CNPhoneNumber

                            contactNumber = phoneNumber.stringValue

                            // Clean the number
                            let stringArray = contactNumber!.componentsSeparatedByCharactersInSet(
                                NSCharacterSet.decimalDigitCharacterSet().invertedSet)
                            let newString = "1" + stringArray.joinWithSeparator("")

                            let firebaseFriendNumber = oneSnapshot.value["phoneNumber"] as! String

                            if newString == firebaseFriendNumber {

                                self.friends.append(Friend(userName: oneSnapshot.value["userName"] as! String,phoneNumber: firebaseFriendNumber, status: 2, name: oneContact.givenName, userID: oneSnapshot.key))

                                // Remove that contact
                                self.contacts.removeObject(oneContact)
                            }
                        }
                    }
                }

                // Now do the users search:
                for oneContact in self.contacts {
                    for oneContactNumer in oneContact.phoneNumbers {

                        let phoneNumber = oneContactNumer.value as! CNPhoneNumber

                        contactNumber = phoneNumber.stringValue

                        let stringArray = contactNumber!.componentsSeparatedByCharactersInSet(
                            NSCharacterSet.decimalDigitCharacterSet().invertedSet)
                        let newString = "1" + stringArray.joinWithSeparator("")

                        let usersURL: Firebase! = Firebase(url: firebaseMainURL + "presentUserIDUserNameByPhoneNumber/" + newString)

                        // Check db:

                        usersURL.observeSingleEventOfType(.Value, withBlock: { snapshot in

                            if snapshot.childrenCount > 1 {

                                // They are users (but not your friends):
                                self.friends.append(Friend(userName: snapshot.value["userName"] as! String, phoneNumber: snapshot.key, status: 1, name: "test", userID: snapshot.value["userID"] as! String))

                                let userName = snapshot.value["userName"] as! String

                                print("Friends name: " + userName)

                                // Remove that contact
                                self.contacts.removeObject(oneContact)

                            }

                        })

                    }

                }

            })

How can I test and check when the second, on usersURL, has completed before allowing other actions to occur in app?

1条回答
家丑人穷心不美
2楼-- · 2019-07-09 05:29

One approach to signal completion of an asynchronous function is using a completion handler. You already used completion handlers in the Firebase API and there are many APIs in the system frameworks, so I don't explain that further.

Given this approach, wrap your code into a function, say updateContacts with a completion handler. Usually an asynchronous function returns the computed value or an error. In some cases, it just succeeds or fails - without returning a value. You express this in the signature of the completion handler. Your function updateContacts may not compute a value, but it may fail or succeed anyway. Then, you can use an optional error: if it is nil, the task succeeded, otherwise it contains the error that occurred.

When your underlying task has been completed, call the completion handler with the result.

Note: You must ensure, that the completion handler will be eventually called!

func updateContacts(completion: (ErrorType?)-> ()) {
    friendsURL.observeSingleEventOfType(.Value, withBlock: { snapshot in
        ...
            ...
                ...
                    usersURL.observeSingleEventOfType(.Value, withBlock: { snapshot in
                        ...
                        let error = // nil or an error
                        completion(error)
                       return
                     }
        completion(nil)
    }    
}

Now, when you have an array of asynchronous subtasks, that will be called in parallel and you want to signal completion of updateContacts when all subtasks have been completed - you can utilise dispatch groups:

let group = dispatch_group_create()
var error: ErrorType?
contactNumbers.forEach { number in
    dispatch_group_enter(group)
    queryAsync(number) { (result, error) in
        if let error = error {
            // handle error
        } else {
            ...
        }             
        dispatch_group_leave(group)
    }
}
dispatch_group_notify(group, queue) {
    // call the completion handler of your function `updateContacts`:
    completion(error)
}
查看更多
登录 后发表回答