After several hours of trying to figure out what's happening without finding an answer to fill my void anywhere, I finally decided to ask here. While I assume I do have a concurrency issue, I have no clue as to how to solve it...
I have an application trying to pull data from a Firebase Realtime Database with the following content:
{
"products" : {
"accessory" : {
"foo1" : {
"ean" : 8793462789134,
"name" : "Foo 1"
},
"foo2" : {
"ean" : 8793462789135,
"name" : "Foo 2"
}
},
"cpu" : {
"foo3" : {
"ean" : 8793462789115,
"name" : "Foo 3"
}
},
"ios" : {
"foo4" : {
"ean" : 8793462789120,
"name" : "Foo 4"
},
"foo5" : {
"ean" : 8793462789123,
"name" : "Foo 5"
}
}
}
}
I have a data model in Product.swift:
class Product {
var identifier: String
var category: String
var ean: Int
var name: String
init(identifier: String, category: String, ean: Int, name: String) {
self.init(identifier: identifier)
self.category = category
self.ean = ean
self.name = name
}
}
I want to fetch the data in another class called FirebaseFactory.swift. I plan to use to communicate with Firebase:
import Foundation
import FirebaseDatabase
class FirebaseFactory {
var ref = Database.database().reference()
func getAvailableProducts() -> [Product] {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// 1st print statement
print("From within closure: \(data)")
}
// 2nd print statement
print("From outside the closure: \(data)")
// Getting the products yet to be implemented...
return products
}
}
For now, I am simply trying to call the getAvailableProducts() -> [Product]
function from one of my view controllers:
override func viewWillAppear(_ animated: Bool) {
let products = FirebaseFactory().getAvailableProducts()
}
My Problem now is that the 2nd print is printed prior to the 1st – which also means that retrieving the data from the snapshot and assigning it to data
variable does not take place. (I know that the code to create my Product objects is missing, but that part actually is not my issue – concurrency is...)
Any hints – before I pull out any more of my hairs – is highly appreciated!!
You're on the right track with your theory: the behavior you're describing is how asynchronous data works with closures. You've experienced how this causes problems with returning the data you want. It's a very common question. In fact, I wrote a blog on this recently, and I recommend you check it out so you can apply the solution: incorporating closures into your functions. Here's what that looks like in the particular case you've shown:
func getAvailableProducts(completion: @escaping ([Product])-> Void) {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// do whatever you were planning on doing to return your data into products... probably something like
/*
for snap in snapshot.children.allObjects as? [DataSnapshot] {
let product = makeProduct(snap)
products.append(product)
}
*/
completion(products)
}
}
Then in viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
FirebaseFactory().getAvailableProducts(){ productsArray in
// do something with your products
self.products = productsArray
// maybe reload data if you have a tableview
self.tableView.reloadData()
}
}