単一のイベントを繰り返し監視する代わりにクエリを使用して、ソーシャルネットワークアプリの投稿の取得を高速化します


97

/ posts / id /(投稿情報)のような私のソーシャルネットワークの投稿オブジェクトにつながるキーの配列があります

投稿をロードするとき、/ posts / 0をロードし、次に/ posts / 1などをobserveSingleEventOfType(.Value)メソッドを使用してロードします。

lazyTableViewは一度に30をロードするためにを使用しますが、それはかなり遅いです。JSONツリーのデータを再構成する必要がある場合でも、クエリメソッドの1つを使用する方法、またはそれを高速化する別の方法はありますか?

私は自分のアプリを再実装したParseから来ており、これまでのところ非常に良い経験をしています。ちょうどこれに私は少しこだわっています。助けてくれてありがとう!

編集:

func loadNext(i: Int) { 

    // check if exhists
    let ideaPostsRef = Firebase(url: "https://APPURL")

    ideaPostsRef.childByAppendingPath(i.description).observeSingleEventOfType(.Value, withBlock: {
        (snapshot) in

        if i % 29 == 0 && i != 0 && !self.hitNull { return }
            // false if nil
            // true if not nil
        if !(snapshot.value is NSNull) {
            let postJSON  = snapshot.value as! [String: AnyObject]
            print("GOT VALID \(postJSON)")
            let post = IdeaPost(message: postJSON["message"] as! String, byUser: postJSON["user"] as! String, withId: i.description)
            post.upvotes = postJSON["upvotes"] as! Int
            self.ideaPostDataSource.append(post)
            self.loadNext(i + 1)
        } else {
            // doesn't exhist
            print("GOT NULL RETURNING AT \(i)")
            self.doneLoading = true
            self.hitNull = true
            return
        }
    }
}

この再帰関数は基本的に実行され、firebaseからキー番号iの値を取得します。NSNULLの場合、それが読み込み可能な最後のポストであることを認識し、二度とロードしません。NSNULLがヒットしない場合はi % 29 == 0、ベースケースとして返されるため、一度に読み込まれる投稿は30のみです(0インデックス付き)。私が設定した場合doneLoadingtruetableView.reloadData()プロパティのオブザーバを使用して呼び出されます。

これは私がフェッチしている配列のサンプルです

"ideaPosts" : [ {
    "id" : 0,
    "message" : "Test",
    "upvotes" : 1,
    "user" : "Anonymous"
  }, {
    "id" : 1,
    "message" : "Test2",
    "upvotes" : 1,
    "user" : "Anonymous"
  } ]

1
コードを説明する代わりにコードを提示すると、はるかに容易になります。質問に問題を再現するための最小限のJSON(スクリーンショットではなくテキストとして)とコードを含めてください。改善する方法を確認できます。MCVEの詳細をご覧ください。
フランクファンPuffelen

コードの説明を含むように編集
Big_Mac

回答:


123

更新:AskFirebaseエピソードでこの質問についても説明します。

リクエストをパイプライン処理できるので、Firebaseから多くのアイテムをロードするのが遅くなる必要はありません。しかし、あなたのコードはこれを不可能にしています、それは実際に次善のパフォーマンスにつながります。

コードでは、サーバーにアイテムを要求し、そのアイテムが返されるのを待ってから、次のアイテムをロードします。次のような簡略化されたシーケンス図では、

Your app                     Firebase 
                             Database

        -- request item 1 -->
                               S  L
                               e  o
                               r  a
                               v  d
                               e  i
        <-  return item  1 --  r  n
                                  g
        -- request item 2 -->
                               S  L
                               e  o
                               r  a
                               v  d
                               e  i
                               r  n
        <-  return item  2 --     g
        -- request item 3 -->
                 .
                 .
                 .
        -- request item 30-->
                               S  L
                               e  o
                               r  a
                               v  d
                               e  i
                               r  n
                                  g
        <-  return item 30 --

このシナリオでは、往復時間の30倍+ディスクからデータをロードするのにかかる時間の30倍待機しています。(簡単にするために)往復に1秒かかり、ディスクからのアイテムのロードにも最低1秒かかるとすると、30 *(1 + 1)= 60秒になります。

Firebaseアプリケーションでは、すべてのリクエスト(または少なくとも適切な数のリクエスト)を一度に送信すると、パフォーマンスが大幅に向上します。

Your app                     Firebase 
                             Database

        -- request item 1 -->
        -- request item 2 -->  S  L
        -- request item 3 -->  e  o
                 .             r  a
                 .             v  d
                 .             e  i
        -- request item 30-->  r  n
                                  g
        <-  return item  1 --     
        <-  return item  2 --      
        <-  return item  3 --
                 .
                 .
                 .
        <-  return item 30 --

再び1秒のラウンドトリップと1秒のロードを想定すると、30 * 1 + 1 = 31秒待機します。

つまり、すべてのリクエストは同じ接続を経由します。唯一の違い、ということを考えるとget(1)get(2)get(3)とは、getAll([1,2,3])フレームのためのいくつかのオーバーヘッドです。

私はjsbinをセットアップして動作を説明します。データモデルは非常に単純ですが、違いを示しています。

function loadVideosSequential(videoIds) {
  if (videoIds.length > 0) {
    db.child('videos').child(videoIds[0]).once('value', snapshot => {
      if (videoIds.length > 1) {
        loadVideosSequential(videoIds.splice(1), callback)
      }
    });
  }
}

function loadVideosParallel(videoIds) {
  Promise.all(
    videoIds.map(id => db.child('videos').child(id).once('value'))
  );
}

比較すると、私のシステムでは64個のアイテムを順次ロードするのに3.8秒かかりますが、(Firebaseクライアントがネイティブに行うように)パイプラインでロードするのに600ミリ秒かかります。正確な数は接続(レイテンシと帯域幅)によって異なりますが、パイプラインバージョンは常に大幅に高速になります。


12
いいね、プフ!また、すべての項目をロードする必要があるが、何らかのアクションを実行する前に並行してそれらを取得したい場合は、promiseのチェーン(jQuery.whenAll()、q.all()、またはPromise.all())が非常に便利です。
加藤

5
涼しい。私はそれを使っていたのに、それを考えさえしなかった。:-)
フランク・ファン・プフェレン

2
@FrankvanPuffelenパフォーマンスの観点からは正しいですが、これらの呼び出しのいずれかが何らかのエラーのために戻らなかった場合はどうなりますか?これらのうちのいずれかが失敗した場合、保留中の要求の残りを「キャンセル」するにはどうすればよいですか。順次リクエストの場合、どのリクエストが失敗したかをコードで知ることができます。あなたの考えを共有してください。ありがとう。
ペリー

1
Promise.all()メソッドは拒否する最初のプロミスの理由で拒否します。」
ペジャロ2017年

4
AndroidでPromise.allを実行するにはどうすればよいですか?Androidですべてのデータをロードする方法
Muhammad chhota '23
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.