I have a simple function loading data from Firebase.
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
Currently this function returns nil
always, even though there is data to load. It does this because it doesn't ever get to the perform the completion block where it loads the array before the function returns. I'm looking for a way to make the function only return once the completion block has been called but I can't put return in the completion block.
(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)
You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.
I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.
The key part of that is the function
downloadFileAtURL
, which takes a completion handler and does an async download:The code above uses Apple's
URLSession
class to download data from a remote server asynchronously. When you create adataTask
, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to
DispatchQueue.main.async() {}
.Back to the OP's code:
I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.
Reworking the code from @Raghav7890's answer to use a typealias:
I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.
Edit:
Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.
In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread: