2017-02-28 138 views
1

我的數據結構是類似以下內容:檢索從火力地堡數據,而在另一個火力地堡數據庫參考使用它

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」登錄和管理的菜單他們各自的餐廳。但是我有下面的代碼的問題:(注意fetchDish函數被調用viewDidLoad中)

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對我來說是非常新的,我完全不知道應該如何設計我的數據結構。感謝您的幫助,如果您需要其他信息,請告訴我。

UPDATE:

問題可通過改變我的數據結構是什麼周杰倫曾建議解決。下面的代碼是對我工作:(修改周杰倫的代碼位)

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

請刪除DispatchQueue.main .async。這不是必需的。 – Jay

+0

使用DispatchQueue.main.async是否有任何特定的負面影響?當我將self.tableview.reloadData()放入其中時,它明顯加快了表加載信息的時間。 – JustTro11

+0

在這個應用程序中,它不應該有任何明顯的性能差異,因爲在從服務器收到Firebase數據並處理閉包中的代碼之前,tableView不會被刷新。在這種情況下,您會在主串行隊列上拋出一個異步任務,這會使任務按順序排在前面。即不需要,因爲Firebase功能已經是異步的。 – Jay

回答

0

幾件事情:

火力地堡是異步的,你必須考慮到,在你的代碼。就像在帖子中一樣,第二個Firebase函數可能會在第一個Firebase函數成功返回數據之前執行,即當第二個調用發生時,restaurantName可能爲零。

你應該嵌套你的調用(在這個用例中)以確保在使用它之前數據是有效的。這樣的..並保持閱讀

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。

在這種情況下,如果腹部巴斯特漢堡改變腰部減肥漢堡,我們可以做一個改變,它已完成,任何引用它的東西也會更新。如果擁有者改變了,那麼只需更改擁有者的uid即可。

如果餐廳名稱更改爲Tony's Taco Tavern,只需更改子節點即可。

希望有幫助!

編輯:回答一個評論:

爲了獲取字符串(即鑰匙的 '鑰匙':值對)立即.childByAutoId()創建

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

非常感謝Jay的幫助。真的很感激它。 – JustTro11

+0

後續問題:有沒有辦法立即存儲由.childByAutoId生成的字符串,以便我可以在以下參考中使用它?或者我會不得不再次做這個嵌套函數?我知道一個快速解決方法是使用NSUUID()。uuidString,但它看起來不夠優雅。 – JustTro11

+0

@ JustTro11當然!超級簡單!我在答案的尾部添加了一些代碼。 – Jay