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

Posted

技术标签:

【中文标题】从 Firebase 检索数据并在另一个 Firebase 数据库参考中使用它【英文标题】:Retrieving data from Firebase and using it in another Firebase database reference 【发布时间】:2017-02-28 17:39:27 【问题描述】:

我的数据结构如下:

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(在数据库引用中)的值为零。我尝试放置一些断点,似乎第二个数据库引用的第一行是在第一个数据库引用“内”的任何内容之前操作的,所以基本上 restaurantName 在存储任何值之前被调用。

为什么会这样?我该如何解决这个问题?另外,如果我做错了,实现这一目标的最佳做法是什么?

NoSQL 对我来说非常陌生,我完全不知道应该如何设计我的数据结构。提前感谢您的帮助,如果您需要任何其他信息,请告诉我。

更新:

通过将我的数据结构更改为 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)

【问题讨论】:

请删除 DispatchQueue.main.async。不需要。 使用 DispatchQueue.main.async 是否有任何特定的负面影响?当我将 self.tableview.reloadData() 放入其中时,它显着加快了表格加载信息的时间。 在这个应用程序中,它不应该对性能产生任何明显的影响,因为在从服务器接收到 Firebase 数据并处理闭包中的代码之前,不会刷新 tableView。在这种情况下,您将在主串行队列上抛出一个异步任务,该任务将任务按顺序排列在其前面的任何内容之后。即不需要,因为 Firebase 函数已经是异步的。 【参考方案1】:

有几点:

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 更改为 Waist Slimming Burger,我们可以进行一次更改并完成,并且任何引用它的内容也会更新。与所有者相同,如果所有者更改,则只需更改所有者 uid。

如果餐厅名称更改为 Tony's Taco Tavern,只需更改子节点即可。

希望有帮助!

编辑:回复评论:

获取由 .childByAutoId() 立即创建的字符串(即键:值对的“键”)

    let testRef = ref.child("test").childByAutoId()
    let key = testRef.key
    print(key)

【讨论】:

非常感谢杰伊的帮助。真的很感激。 后续问题:有没有办法立即存储由 .childByAutoId 生成的字符串,以便我可以在以下参考中使用它?还是我必须再次执行此嵌套函数?我知道一个快速的解决方法是使用 NSUUID().uuidString,但它似乎不够优雅。 @JustTro11 确定!超级简单!我在答案的末尾添加了一些代码。 @Jay 你能帮我解答一下吗?我需要从一个已经链接到一个 firebase 项目的项目中访问另一个 firebase 数据库,我不知道从这里去哪里,或者我是否认为必须为 2 个 ios 应用程序建立数据库是一个好主意。 ***.com/questions/44866220.

以上是关于从 Firebase 检索数据并在另一个 Firebase 数据库参考中使用它的主要内容,如果未能解决你的问题,请参考以下文章

如何从firebase数据中检索数据短片

从 firebase 数据库中检索数据并在 Objective-C 中的 UITable 中显示

Swift:从 Firebase 检索数据并在表格视图中显示

Android 从片段中检索 Json 并在另一个活动中使用

使用 Alamofire 从 Firebase 检索 JSON

使用 html 链接检索 JSON 对象并在另一个 html 页面上显示 json 数据