Beschleunigen Sie das Abrufen von Posts für meine App für soziale Netzwerke, indem Sie Abfragen verwenden, anstatt ein einzelnes Ereignis wiederholt zu beobachten

97

Ich habe eine Reihe von Schlüsseln, die dazu führen, dass Objekte für mein soziales Netzwerk wie so / posts / id / (Post-Info) veröffentlicht werden.

Wenn ich die Beiträge lade, lade ich / posts / 0 und dann / posts / 1 usw. mit der observeSingleEventOfType(.Value)Methode.

Ich benutze a lazyTableView, um 30 auf einmal zu laden und es ist ziemlich langsam. Gibt es eine Möglichkeit, eine der Abfragemethoden oder eine andere Methode zu verwenden, um sie schneller zu machen, selbst wenn ich die Daten in meinem JSON-Baum umstrukturieren muss?

Ich komme von Parse und implementiere meine App neu. Bisher war die Erfahrung recht gut. Nur diese eine Sache bin ich ein bisschen fest. Vielen Dank im Voraus für die Hilfe!

BEARBEITEN:

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

Diese rekursive Funktion wird im Wesentlichen ausgeführt, um den Wert für die Schlüsselnummer i von der Firebase abzurufen. Wenn es NSNULL ist, weiß es, dass dies der letzte mögliche Beitrag ist, der geladen wird, und wird es nie wieder tun. Wenn NSNULL nicht getroffen wird, aber i % 29 == 0als Basisfall zurückgegeben wird, werden jeweils nur 30 Beiträge geladen (0 indiziert). Als ich festgelegt doneLoadingauf true, tableView.reloadData()wird als eine Eigenschaft Beobachter mit.

Hier ist ein Beispiel dafür, wie das Array, das ich abrufe, aussieht

"ideaPosts" : [ {
    "id" : 0,
    "message" : "Test",
    "upvotes" : 1,
    "user" : "Anonymous"
  }, {
    "id" : 1,
    "message" : "Test2",
    "upvotes" : 1,
    "user" : "Anonymous"
  } ]
Big Mac
quelle
1
Es ist viel einfacher zu helfen, wenn Sie uns Ihren Code zeigen, anstatt ihn zu beschreiben. Fügen Sie den minimalen JSON-Wert (als Text, kein Screenshot) und Code ein, um das Problem in Ihrer Frage zu reproduzieren, und wir können sehen, wie es verbessert werden kann. Lesen Sie mehr über eine MCVE .
Frank van Puffelen
Bearbeitet, um Code-Erklärung
einzuschließen

Antworten:

123

Update: Wir behandeln diese Frage jetzt auch in einer AskFirebase-Episode .

Das Laden vieler Elemente aus Firebase muss nicht langsam sein, da Sie die Anforderungen weiterleiten können. Ihr Code macht dies jedoch unmöglich, was in der Tat zu einer suboptimalen Leistung führt.

In Ihrem Code fordern Sie ein Element vom Server an, warten auf die Rückgabe dieses Elements und laden dann das nächste. In einem vereinfachten Sequenzdiagramm, das wie folgt aussieht:

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

In diesem Szenario warten Sie auf das 30-fache Ihrer Hin- und Rückfahrt + das 30-fache der Zeit, die zum Laden der Daten von der Festplatte benötigt wird. Wenn wir der Einfachheit halber sagen, dass Roundtrips 1 Sekunde dauern und das Laden eines Elements von der Festplatte auch eine Sekunde dauert, die mindestens 30 * (1 + 1) = 60 Sekunden beträgt.

In Firebase-Anwendungen erzielen Sie eine viel bessere Leistung, wenn Sie alle Anforderungen (oder zumindest eine angemessene Anzahl davon) auf einmal senden:

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

Wenn wir erneut von einer Hin- und Rückfahrt von 1 Sekunde und einer Ladezeit von 1 Sekunde ausgehen, warten Sie auf 30 * 1 + 1 = 31 Sekunden.

Also: Alle Anfragen gehen über dieselbe Verbindung. In Anbetracht , dass der einzige Unterschied zwischen get(1), get(2), get(3)und getAll([1,2,3])ist ein gewisser Overhead für die Rahmen.

Ich habe einen jsbin eingerichtet, um das Verhalten zu demonstrieren . Das Datenmodell ist sehr einfach, zeigt aber den Unterschied.

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

Zum Vergleich: Das sequentielle Laden von 64 Elementen auf meinem System dauert 3,8 Sekunden, während das Laden über eine Pipeline (wie der Firebase-Client nativ) 600 ms dauert. Die genauen Zahlen hängen von Ihrer Verbindung ab (Latenz und Bandbreite), aber die Pipeline-Version sollte immer deutlich schneller sein.

Frank van Puffelen
quelle
12
Schön, Puf! Auch das Verketten von Versprechungen (jQuery.whenAll (), q.all () oder Promise.all ()) kann hier sehr nützlich sein, wenn Sie alle Elemente laden müssen, sie aber dennoch parallel abrufen möchten, bevor Sie Maßnahmen ergreifen.
Kato
4
Cool. Ich habe nicht einmal daran gedacht, obwohl ich es benutzt habe. :-)
Frank van Puffelen
2
@FrankvanPuffelen Sie sind aus Sicht der Leistung richtig, aber was ist, wenn einer dieser Aufrufe aufgrund eines Fehlers nicht zurückgegeben wurde? Wie können Sie den Rest der ausstehenden Anforderungen "abbrechen", wenn jemand von diesen fehlschlägt? Bei sequentiellen Anfragen können wir im Code erfahren, welche Anfrage fehlgeschlagen ist. Bitte teilen Sie Ihre Gedanken. Vielen Dank.
Perry
1
" Die Promise.all () -Methode [...] lehnt mit dem Grund des ersten Versprechens ab, das abgelehnt wird ."
Pejalo
4
Wie können wir Promise.all in Android machen? Wie wir alle Daten in Android laden können
Muhammad Chhota