从Firebase检索数据并在另一个Firebase数据库引用中使用它

时间:2017-02-28 17:39:28

标签: ios firebase swift3 firebase-realtime-database nosql

我的数据结构类似于以下内容:

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)
}

1 个答案:

答案 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)