Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Dispatch Groups Swift

When a user creates a new group in my app I have to push invites to the database as well as other information. I've started using Dispatch Groups in order to keep track of when all the information is successfully sent out so I can dismiss the view.

I'm trying to use a dispatch group for the invites and another dispatch group for all the data. Here's what I have:

// Push new data to db
func createGroup(onSccess completion:@escaping () -> Void) {
    let inviteDispatchGroup = DispatchGroup()
    let dataDispatchGroup = DispatchGroup()

    let uid = FIRAuth.auth()?.currentUser?.uid
    let name = String(uid!) + "_" + nameTextField.text!

    // push invites
    dataDispatchGroup.enter()
    for invite in invites {
        inviteDispatchGroup.enter()
        let ref = FIRDatabase.database().reference().child("users").child(invite.id).child("invites")
        ref.updateChildValues([name: nameTextField.text!]) { (error, ref) -> Void in
            inviteDispatchGroup.leave()
        }
    }
    inviteDispatchGroup.notify(queue: DispatchQueue.main, execute: {
        dataDispatchGroup.leave()
    })

    // store picture
    dataDispatchGroup.enter()
    let storageRef = FIRStorage.storage().reference().child("profile_images").child("\(name).png")
    if let uploadData = UIImagePNGRepresentation(profImage.resizeImage(targetSize: CGSize(width: 500, height: Int(500*(profImage.size.height/profImage.size.width))))) {
        storageRef.put(uploadData, metadata: nil, completion: { (metadata, error) in
            dataDispatchGroup.leave()
        })
    }

    // store pet info
    dataDispatchGroup.enter()
    let petRef = FIRDatabase.database().reference().child("pets").child(name)
    petRef.setValue(["mod":uid!, "name":nameTextField.text!, "members":[uid!]]) { (error, ref) -> Void in
        dataDispatchGroup.leave()
    }

    // store user info
    dataDispatchGroup.enter()
    let userRef = FIRDatabase.database().reference().child("users").child(uid!).child("pets")
    userRef.updateChildValues([name: true]) { (error, ref) -> Void in
        dataDispatchGroup.leave()
    }

    dataDispatchGroup.notify(queue: DispatchQueue.main, execute: {
        completion()
    })
}

As you can see, when the invitesDipatchGroup is completed it's corresponding dataDispatchGroup is left.

I'm new to Dispatch groups and want to hear if this is the correct approach to be taking with this sort of task.

like image 714
MarksCode Avatar asked Jan 30 '17 21:01

MarksCode


1 Answers

This is a very good approach for async task tracking.

It is important to check if all code paths are covered with leave(). You have a potential bug, if the if let doesn't have the value not optional. Fixed here:

func createGroup(onSccess completion:@escaping () -> Void) {
    [...]
    // store picture
    dataDispatchGroup.enter()
    let storageRef = FIRStorage.storage().reference().child("profile_images").child("\(name).png")
    if let uploadData = UIImagePNGRepresentation(profImage.resizeImage(targetSize: CGSize(width: 500, height: Int(500*(profImage.size.height/profImage.size.width))))) {
        storageRef.put(uploadData, metadata: nil, completion: { (metadata, error) in
            dataDispatchGroup.leave()
        })
    } else {
        dataDispatchGroup.leave()
    }
    [...]
}

In general always also make sure that all completion blocks are called in all of the code paths of methods you use. There always may be a bug in Firebase or there may be something about the completion not being called in the documentation of those methods. But again, this is the way to go.

like image 75
Alistra Avatar answered Nov 02 '22 20:11

Alistra