Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: For Loop Wait Until Response And Return Value According To Response

I'm new to swift and I'm practicing networking.

I have a list of kids ID's. Full kids info is stored in a Firebase Database. Each kid in the database has an ID and a property isGamer: Bool. I want to create a function which will return the first kid where isGamer==true. The logic I was thinking about is: A for loop, which will run on the list and will check for the first kid if isGamer == true if so, return kid's ID, else continue searching. I think it would work though I have no idea how to deal with all these asynchronous stuff, how can I make a for loop wait for response etc.

The main function will manage everything:

// Mother Function
func printFirstGamer(kidsIds: [String]) {
     print(returnFirstKidIsGamer(kidsIds: kidsIds ))
}

Another function will find the first kid where isGamer==true:

// This is where I'm stuck basically

func returnFirstKidIsGamer(kidsIds: [String]) -> String {
    for kidID in kids {
        // Wait for previous request to finish before trying again.
        DataService.instance.isKidAGamer(kidID: kidID) { (isGamer) in

        }
        // The for loop will wait for response and then if isGamer==false, will continue, if isGamer==true will return 
    }
    // return first kid where isGamer == true

}

I will use this function to check isGamer for each kid:

func isKidAGamer(kidID: String, completed: @escaping isKidAGamerCompleted) {
        mainRef.child("Kids").child(kidID).child("isGamer").observeSingleEvent(of: .value, with: { (result) in
            if result.exists() {
                let isGamer = result.value as! Bool
                completed(isGamer)
            } else {
                completed(false)
            }
        })
    }

note: I don't want to use queries, I'm practicing (:

Thanks (:

like image 899
Gal Shahar Avatar asked Sep 14 '25 09:09

Gal Shahar


1 Answers

It's possible to make this synchronous using a DispatchGroup, but this must never be called on the main queue.

// Blocking function. Must not be called on main queue!
func returnFirstKidIsGamer(kidsIds: [String]) -> String? {
    let group = DispatchGroup()
    var result: String? = nil

    for kidID in kidsIds {
        // Wait for previous request to finish before trying again.
        group.enter()
        DataService.instance.isKidAGamer(kidID: kidID) { (isGamer) in
            if isGamer {
                result = kidId
            }
            group.leave()
        }
        group.wait()
        guard result == nil else { break }
    }
    return result
}

This calls group.enter() before entering each loop, and group.leave() when each step completes. It then waits for the step to complete before moving on.

This function is synchronous. It blocks the queue and so must never be called on the main queue. You have to move it to the background with something like this:

DispatchQueue.global(qos: .userInitiated).async {
    let kidId = returnFirstKidIsGamer(kidsIds: [kids])
    DispatchQueue.main.async {
        doSomethingInTheUIWithValue(kidId)
    }
}

Note that this returns String?, not String, since no id may be found.

As a rule, you shouldn't do this. You should use a query. But this is how you make asynchronous functions into synchronous functions when needed.

like image 81
Rob Napier Avatar answered Sep 16 '25 09:09

Rob Napier



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!