3
\$\begingroup\$

I'm learning swift programming and i built a music trivia app for iOS that use firebase real time database. I have a function that before the game start get the number of record for each category of the game and update a class variable that is a tuple called records.

var records = (classical: 0, mix: 0, casual: 0)
private func getNumberOfRecords(category: String) {
 var ref: DatabaseReference!
 ref = Database.database().reference().child(category)
 ref.observeSingleEvent(of: .value) { snapshot in
 let enumerator = snapshot.children
 while((enumerator.nextObject() as? DataSnapshot) != nil) {
 switch category {
 case "casual":
 records.casual += 1
 
 case "classical":
 records.classical += 1
 
 case "mix":
 records.mix += 1
 
 default: print("Error loading record from category \(category)")
 }
 }
 }
}

Here is how I call the function and because it's asynchronous the response I receive from firebase and I need to know the number of records before call another function that will load the game data from the database i use DispatchQueue and wait 3 seconds.

getNumberOfRecords(category: "classical")
getNumberOfRecords(category: "casual")
getNumberOfRecords(category: "mix")
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
 loadDataFromDB(records: records.classical, category: "classical")
 loadDataFromDB(records: records.casual, category: "casual")
 loadDataFromDB(records: records.mix, category: "mix")
}

Now this is working but it's pretty ugly, especially using the function getNumberOfRecords to update the global tuple records. I don't know how can I rewrite this because if i make getNumberOfRecords return a tuple rather than update the global one is always 0 when i pass it as parameter of the function loadDataFromDB inside the DispatchQueue.

Thanks in advance to anyone will have time to read this.

asked Oct 30, 2020 at 19:54
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

A typical way to write an asynchronous function is to have it take a function as an argument that will be called with the results when they are ready. Such a function is often called a completion handler.

private func getNumberOfRecords(category: String, completion: (Int) -> Void) { 
 ...
 default: print("Error loading record from category \(category)")
 }
 }
 }
 completion(records.count)
}

Then at the call site you can write:

getNumberOfRecords(category: "classical", completion: { count in ... })

or better written with trailing-closure syntax:

getNumberOfRecords(category: "classical") { count in ... }

You still have the problem of three asynchronous things, and instead of chaining them together, you can also avoid looping over the data 3 times, you can loop over the data once and remember the counts for each item, using a dictionary:

private func getNumberOfRecords(completion: ([String: Int] -> Void)) {
 var categoryCounts: [String: Int] = [:]
 {
 ... loop over the data here and instead of switch category ...
 categoryCounts[category] = (categoryCounts[category] ?? 0) + 1
 }
 completion(categoryCounts)
}
getNumberOfRecords { categoryCounts in 
 for (category, count) in categoryCounts {
 loadDataFromDB(records: count, category: category) 
 }
}
answered Nov 3, 2020 at 15:26
\$\endgroup\$
1
  • \$\begingroup\$ Thank you! the completion handler is exactly what I was looking for. I was trying to solve this using promises but I think it's more important to learn how to write an asynchronous function without any external library first. Again thanks for your time! \$\endgroup\$ Commented Nov 6, 2020 at 19:54

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.