我的数据结构类似于以下内容:
restaurant_owners
|
|owner_id (a unique ID)
|
|restaurant_name
|email
restaurant_menus
|
|restaurant_name
|
|dish_type (drinks, appetizer, etc...)
|
|dish_id (a unique ID)
|
|name
|
|price
该应用程序的想法基本上是允许" restaurant_owners"登录和管理各自餐厅的菜单。但是我遇到以下代码的问题:(注意在viewDidLoad中调用fetchDish函数)
func fetchDish() {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
//first time referencing database
FIRDatabase.database().reference().child("owners").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
DispatchQueue.main.async{
restaurantName = dictionary["name"] as? String
print(restaurantName!)
}
}
})
//second time referencing database
FIRDatabase.database().reference().child("restaurants").child(restaurantName!).child("appetizer").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}, withCancel: nil)
}
我要做的是检索当前登录用户的餐馆名称,并将其存储在变量" restaurantName"中。然后,当我第二次引用数据库时,我可以在.child中使用这个变量(例如:.child(restaurantName))。
但是,当我运行它时,我收到一个错误,指出restaurantName(在数据库引用中)的值为nil。我尝试放入一些断点,似乎第二个数据库引用的第一行是在""之前运行的。第一个数据库引用,所以在任何值存储到它之前,基本上会调用restaurantName。
为什么会这样?我该如何解决这个问题?另外,如果我完全错误的话,实现这一目标的最佳做法是什么?
NoSQL对我来说是一个新手,我完全不知道应该如何设计我的数据结构。感谢您的帮助,如果您需要任何其他信息,请告诉我。
更新
通过将我的数据结构更改为Jay所建议的问题,解决了这个问题。以下代码对我有用:(稍微修改了Jay的代码)
func fetchOwner() {
let uid = FIRAuth.auth()?.currentUser?.uid
let ownersRef = FIRDatabase.database().reference().child("owners")
ownersRef.child(uid!).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantID = dict["restaurantID"] as! String
self.fetchRestaurant(restaurantID: restaurantID)
}
}, withCancel: nil)
}
func fetchRestaurant(restaurantID: String) {
let restaurantsRef = FIRDatabase.database().reference().child("restaurants")
restaurantsRef.child(restaurantID).child("menu").observe(.childAdded, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
let dish = Dish()
dish.setValuesForKeys(dictionary)
self.dishes.append(dish)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
答案 0 :(得分:0)
有几件事:
Firebase是异步的,您必须在代码中考虑到这一点。就像在帖子中一样,第二个Firebase函数可以在第一个Firebase函数成功返回数据之前执行,即当第二个调用发生时,restaurantName可能为nil。
您应该嵌套调用(在此用例中)以确保数据在使用之前有效。像这样..并继续阅读
let ownersRef = rootRef.child("owners")
let restaurantRef = rootRef.child("restaurants")
func viewDidLoad() {
fetchOwner("owner uid")
}
func fetchOwner(ownerUid: String) {
var restaurantName: String?
let uid = FIRAuth.auth()?.currentUser?.uid
ownserRef.child(ownerUid).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
restaurantId = dict["restaurant_id"] as? String
fetchRestaurant(restaurantId)
}
}
})
}
func fetchRestaurant(restaurantId: String) {
restaurantRef.child(restaurantId).observeSingleEvent(of: .value, with: { snapshot in
if let dict = snapshot.value as? [String: AnyObject] {
let restaurantName = dict["name"] as! String
let menuDict = dict["menu"] as! [String:Any]
self.dataSourceArray.append(menuDict)
menuTableView.reloadData()
}
}
}
最重要的是,将密钥名称与其包含的数据解除关联几乎总是最佳做法。在这种情况下,您使用餐馆名称作为密钥。如果餐馆名称更改或更新怎么办?你无法改变一把钥匙!唯一的选择是删除它并重新编写它......以及...引用它的数据库中的每个节点。
更好的选择是利用childByAutoId并让Firebase为您命名节点并保留具有相关数据的子节点。
restaurants
-Yii9sjs9s9k9ksd
name: "Bobs Big Burger Barn"
owner: -Y88jsjjdooijisad
menu:
-y8u8jh8jajsd
name: "Belly Buster Burger"
type: "burger"
price: "$1M"
-j8u89joskoko
name: "Black and Blue Burger"
type: "burger"
price: "$9.95"
如您所见,我利用childByAutoId为这家餐厅创建了密钥,以及菜单上的项目。我还在所有者节点中引用了所有者的uid。
在这种情况下,如果Belly Buster Burger更换为腰部减肥汉堡,我们可以进行一次更改并完成,并且任何引用它的内容也会更新。与所有者相同,如果所有者更改,则只需更改所有者uid。
如果餐馆名称改为Tony's Taco Tavern,只需更改子节点即可。
希望有所帮助!
编辑:回答评论:
获取由.childByAutoId()
立即创建的字符串(即键:值对的'键') let testRef = ref.child("test").childByAutoId()
let key = testRef.key
print(key)