2017-02-28 22 views
1

私のデータ構造は、次のようなものです:Firebaseからデータを取得し、それを使用して

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は」のメニューにログインし、管理できるように、基本的ですそれぞれのレストラン。しかし、私は次のコードで問題を抱えています:

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

私は何をしようとしていますが現在ログインしているユーザーのためのレストランの名前を取得することです(fetchDish関数がのviewDidLoadで呼び出されることに注意してください)とそれを変数 "restaurantName"に格納します。その後、私が2回目にデータベースを参照しているときに、この変数を.childの内部で使用することができます(例:.child(レストラン名))。

しかし、これを実行すると、(データベース参照内の)restaurantNameの値がnilであるというエラーが表示されます。いくつかのブレークポイントを入れようとしましたが、第2のデータベース参照の最初の行が最初のデータベース参照の "内側"になる前に操作されているようです。

これはなぜ発生しますか?この問題を回避するにはどうすればよいですか?また、私が完全に間違っている場合、これを達成するためのベストプラクティスは何ですか?

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関数がデータを正常に返す前に、2番目のFirebase関数が実行されます。つまり、2番目の呼び出しが発生すると、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も参照しました。

この場合、ベリーバスターバーガーがウエストスリミングバーガーに変更された場合、1つの変更を行うことができ、それが完了し、それを参照するものも更新されます。所有者と同じこと、所有者が変更された場合、所有者の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

関連する問題