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.

\$\endgroup\$
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) 
    }
}
\$\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\$ – salvatop Nov 6 '20 at 19:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.