Solving the atomic batch request problem in Swift

I run into this problem and speak to people who run into this problem often enough that it needs a bit of documentation.

Say our social network's latest feature allows users to temporarily match with some of their followers, create teams, and start a game all with the tap of a UIButton. The server folks won't get to creating the batch request endpoint for a bit, and anyways it's bad Rails design to combine team and game creation into one controller.
So our Game Service's new feature creates individual match requests for selected followers while leveraging two disparate old features - game and team creation. It's atomic - everything succeeds or nothing is allowed to persist.
Doing all this networking sequentially with no guards assumes that matching-with-followers will return last. Bad. And nesting 2 + N requests would be so slow that I almost consider it even worse.

	//Sequential = dangerous. What if I got a really big team?
	self.match(self.user, follower: person, with:({error in}))
	self.createTeam{team, error in}
	self.createGame(with:newTeam, gameHandler:({success, error in}))
This problem is older than Objective-C and there are more solutions than reverse the string. When I saw this GCD five-line solution, I shift + deleted my Alamofire hack without making a backup.
The solution is just GCD reading our minds as it tends to do, and giving us a simple solution that's flexible, and to this day, has not cost me any trade-offs despite sometimes wildly different flavors of this problem.
Let's give your father's C variables that 21st century typed-functional-language look. dispatch_group_t is now DispatchGroup(), crazier-than-regex ^{}; is now {}. Pet theory: GCD's C syntax drove most people to the gregarious but often useless NSOperationQueue.
For engagement, our boss has decided that all matches should persist and only games and teams should be atomic. Piece of cake, skip, we'll make our game creation conditional on team creation only.

func match(followers:[Person], handler: @escaping(_ totalSuccess: Bool, _ matchesSuccess: Bool) -> Void) {
        let group = DispatchGroup()
        let matchQueue = DispatchQueue(label: "com.mleveton.matchQueue", qos: .default)
        let teamQueue = DispatchQueue(label: "com.mleveton.teamQueue", qos: .default)
        var newTeam:Team?
        
        matchQueue.async {
            for _ in followers{ group.enter() }
            
            for person in followers{
                self.match(self.user, follower: person, with:({ error in
                    //print("match created") //Dozens of matches will be created in the background
                    group.leave()
                }))
            }
        }
        
        //print("about to create team")//Execution will immediately jump to team creation
        teamQueue.async {
            group.enter()
            self.createTeam{team, error in
                if let team = team{newTeam = team}
                group.leave()
            }
        }
        
        //Won't be called until all our matches and our team has been persisted successfully
        group.notify(queue: .main, execute: {
            if let newTeam = newTeam{
                self.createGame(with:newTeam, gameHandler:({ success, error in
                    if error == nil{
                        handler(true, true)
                    }
                }))
            }else{
                //Tell the user and delete the team from the db
                handler(false, true)
            }
        })
    }
We'll pass back two bools and only give the user the thumbs up if both are true (we'll silently make those friendships regardless, Facebook style).
GCD
Queue - executes the block you dispatched to it. We'll give them async closures so that matches and the team are created concurrently.
Group - ensures that all matches and teams are safe in the database before we create the game and start the fun.
*Notice that we loop through followers once to enter the group that many times.
*Swift throws a bad execution error if you try to leave a group more times than you've entered it (which would be big, if true).
*Make sure you leave the group in both the success and error closures or you'll spin forever.
So documented
It's long known that copypasting from Stack without testing is bad. But we know from thermodynamics that all iOS projects converge to The Atomic Batch Request Problem, so solutions that really stand out need more than an upvote.
Copyright © 2011–2018 Mike Leveton