Offline-Funktionen auf Apple-Plattformen

Firebase-Anwendungen funktionieren auch dann, wenn Ihre App vorübergehend die Netzwerkverbindung verliert. Darüber hinaus bietet Firebase Tools zum lokalen Speichern von Daten, zum Verwalten der Präsenz und zum Umgang mit Latenz.

Festplattenpersistenz

Firebase-Apps behandeln vorübergehende Netzwerkunterbrechungen automatisch. Zwischengespeicherte Daten sind offline verfügbar und Firebase sendet alle Schreibvorgänge erneut, wenn die Netzwerkkonnektivität wiederhergestellt ist.

Wenn Sie die Festplattenpersistenz aktivieren, schreibt Ihre App die Daten lokal auf das Gerät, sodass Ihre App den Status beibehalten kann, auch wenn sie offline ist, selbst wenn der Benutzer oder das Betriebssystem die App neu startet.

Sie können die Festplattenpersistenz mit nur einer Codezeile aktivieren.

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
Database.database().isPersistenceEnabled = true

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[FIRDatabase database].persistenceEnabled = YES;

Persistenzverhalten

Durch die Aktivierung der Persistenz bleiben alle Daten, die der Firebase Realtime Database-Client im Online-Modus synchronisiert, auf der Festplatte erhalten und offline verfügbar, selbst wenn der Benutzer oder das Betriebssystem die App neu startet. Das bedeutet, dass Ihre App wie online funktioniert und die im Cache gespeicherten lokalen Daten verwendet. Für lokale Updates werden weiterhin Listener-Rückrufe ausgelöst.

Der Firebase Realtime Database-Client verwaltet automatisch eine Warteschlange aller Schreibvorgänge, die ausgeführt werden, während Ihre App offline ist. Wenn die Persistenz aktiviert ist, wird diese Warteschlange auch auf der Festplatte gespeichert, sodass alle Ihre Schreibvorgänge verfügbar sind, wenn der Benutzer oder das Betriebssystem die App neu startet. Wenn die App wieder eine Verbindung herstellt, werden alle Vorgänge an den Firebase Realtime Database-Server gesendet.

Wenn Ihre App Firebase Authentication verwendet, behält der Firebase Realtime Database-Client das Authentifizierungstoken des Benutzers über alle App-Neustarts hinweg bei. Wenn das Authentifizierungstoken abläuft, während Ihre App offline ist, unterbricht der Client die Schreibvorgänge, bis Ihre App den Benutzer erneut authentifiziert. Andernfalls können die Schreibvorgänge aufgrund von Sicherheitsregeln fehlschlagen.

Daten aktuell halten

Die Firebase-Echtzeitdatenbank synchronisiert und speichert eine lokale Kopie der Daten für aktive Listener. Darüber hinaus können Sie bestimmte Standorte synchronisieren.

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

Der Firebase Realtime Database-Client lädt die Daten an diesen Speicherorten automatisch herunter und hält sie synchron, auch wenn die Referenz keine aktiven Listener hat. Mit der folgenden Codezeile können Sie die Synchronisierung wieder deaktivieren.

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
scoresRef.keepSynced(false)

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[scoresRef keepSynced:NO];

Standardmäßig werden 10 MB zuvor synchronisierter Daten zwischengespeichert. Dies sollte für die meisten Anwendungen ausreichen. Wenn der Cache seine konfigurierte Größe überschreitet, löscht die Firebase-Echtzeitdatenbank die Daten, die zuletzt verwendet wurden. Daten, die synchron gehalten werden, werden nicht aus dem Cache gelöscht.

Daten offline abfragen

Die Firebase-Echtzeitdatenbank speichert von einer Abfrage zurückgegebene Daten zur Offline-Nutzung. Bei offline erstellten Abfragen funktioniert die Firebase-Echtzeitdatenbank weiterhin für zuvor geladene Daten. Wenn die angeforderten Daten nicht geladen wurden, lädt die Firebase Realtime Database Daten aus dem lokalen Cache. Wenn die Netzwerkkonnektivität wieder verfügbar ist, werden die Daten geladen und spiegeln die Abfrage wider.

Dieser Code fragt beispielsweise die letzten vier Elemente in einer Firebase-Echtzeitdatenbank mit Bewertungen ab

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[[[scoresRef queryOrderedByValue] queryLimitedToLast:4]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

Gehen Sie davon aus, dass der Benutzer die Verbindung verliert, offline geht und die App neu startet. Während die App noch offline ist, fragt sie die letzten beiden Elemente vom selben Standort ab. Diese Abfrage gibt die letzten beiden Elemente erfolgreich zurück, da die App alle vier Elemente in der obigen Abfrage geladen hat.

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

Im vorherigen Beispiel löst der Firebase Realtime Database-Client mithilfe des persistenten Caches „Kind hinzugefügt“-Ereignisse für die beiden Dinosaurier mit der höchsten Punktzahl aus. Es wird jedoch kein „Wert“-Ereignis ausgelöst, da die App diese Abfrage nie ausgeführt hat, während sie online war.

Wenn die App die letzten sechs Elemente offline anfordern würde, würde sie sofort „untergeordnete hinzugefügte“ Ereignisse für die vier zwischengespeicherten Elemente erhalten. Wenn das Gerät wieder online ist, synchronisiert sich der Firebase Realtime Database-Client mit dem Server und ruft die letzten beiden Ereignisse „Kind hinzugefügt“ und „Wert“ für die App ab.

Transaktionen offline abwickeln

Alle Transaktionen, die ausgeführt werden, während die App offline ist, werden in die Warteschlange gestellt. Sobald die App die Netzwerkverbindung wiedererlangt, werden die Transaktionen an den Echtzeitdatenbankserver gesendet.

Präsenz verwalten

In Echtzeitanwendungen ist es oft nützlich zu erkennen, wann Clients eine Verbindung herstellen und trennen. Beispielsweise möchten Sie möglicherweise einen Benutzer als „offline“ markieren, wenn sein Client die Verbindung trennt.

Firebase-Datenbank-Clients stellen einfache Grundelemente bereit, die Sie zum Schreiben in die Datenbank verwenden können, wenn ein Client die Verbindung zu den Firebase-Datenbankservern trennt. Diese Aktualisierungen erfolgen unabhängig davon, ob der Client die Verbindung ordnungsgemäß trennt oder nicht. Sie können sich also darauf verlassen, dass sie die Daten bereinigen, selbst wenn eine Verbindung unterbrochen wird oder ein Client abstürzt. Alle Schreibvorgänge, einschließlich Einstellen, Aktualisieren und Entfernen, können bei einer Trennung ausgeführt werden.

Hier ist ein einfaches Beispiel für das Schreiben von Daten beim Trennen der Verbindung mithilfe des onDisconnect Primitivs:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

So funktioniert onDisconnect

Wenn Sie einen onDisconnect() Vorgang einrichten, wird der Vorgang auf dem Firebase Realtime Database-Server gespeichert. Der Server überprüft die Sicherheit, um sicherzustellen, dass der Benutzer das angeforderte Schreibereignis ausführen kann, und informiert Ihre App, wenn es ungültig ist. Der Server überwacht dann die Verbindung. Wenn die Verbindung zu irgendeinem Zeitpunkt abläuft oder vom Realtime Database-Client aktiv geschlossen wird, überprüft der Server die Sicherheit ein zweites Mal (um sicherzustellen, dass der Vorgang noch gültig ist) und ruft dann das Ereignis auf.

Ihre App kann den Rückruf beim Schreibvorgang verwenden, um sicherzustellen, dass onDisconnect korrekt angehängt wurde:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

Ein onDisconnect Ereignis kann auch durch Aufrufen .cancel() abgebrochen werden:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

Verbindungsstatus erkennen

Für viele anwesenheitsbezogene Funktionen ist es hilfreich, dass Ihre App weiß, wann sie online oder offline ist. Firebase Realtime Database bietet einen speziellen Speicherort unter /.info/connected , der jedes Mal aktualisiert wird, wenn sich der Verbindungsstatus des Firebase Realtime Database-Clients ändert. Hier ist ein Beispiel:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
  if snapshot.value as? Bool ?? false {
    print("Connected")
  } else {
    print("Not connected")
  }
})

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    NSLog(@"connected");
  } else {
    NSLog(@"not connected");
  }
}];

/.info/connected ist ein boolescher Wert, der nicht zwischen Echtzeitdatenbank-Clients synchronisiert wird, da der Wert vom Status des Clients abhängt. Mit anderen Worten: Wenn ein Client /.info/connected als „false“ liest, ist dies keine Garantie dafür, dass ein separater Client auch „false“ liest.

Umgang mit Latenz

Server-Zeitstempel

Die Firebase-Echtzeitdatenbankserver bieten einen Mechanismus zum Einfügen von auf dem Server generierten Zeitstempeln als Daten. Diese Funktion bietet in Kombination mit onDisconnect eine einfache Möglichkeit, zuverlässig den Zeitpunkt zu notieren, zu dem ein Realtime Database-Client die Verbindung getrennt hat:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Uhrversatz

Während firebase.database.ServerValue.TIMESTAMP viel genauer und für die meisten Lese-/Schreibvorgänge vorzuziehen ist, kann es gelegentlich nützlich sein, den Zeitversatz des Clients im Vergleich zu den Servern der Firebase Realtime Database abzuschätzen. Sie können einen Rückruf an den Speicherort /.info/serverTimeOffset anhängen, um den Wert in Millisekunden zu erhalten, den Firebase Realtime Database-Clients zur lokal gemeldeten Zeit (Epochenzeit in Millisekunden) hinzufügen, um die Serverzeit zu schätzen. Beachten Sie, dass die Genauigkeit dieses Offsets durch die Netzwerklatenz beeinträchtigt werden kann und daher vor allem zum Erkennen großer (> 1 Sekunde) Abweichungen in der Uhrzeit nützlich ist.

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
  if let offset = snapshot.value as? TimeInterval {
    print("Estimated server time in milliseconds: \(Date().timeIntervalSince1970 * 1000 + offset)")
  }
})

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
FIRDatabaseReference *offsetRef = [[FIRDatabase database] referenceWithPath:@".info/serverTimeOffset"];
[offsetRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  NSTimeInterval offset = [(NSNumber *)snapshot.value doubleValue];
  NSTimeInterval estimatedServerTimeMs = [[NSDate date] timeIntervalSince1970] * 1000.0 + offset;
  NSLog(@"Estimated server time: %0.3f", estimatedServerTimeMs);
}];

Beispiel-Präsenz-App

Durch die Kombination von Trennvorgängen mit der Überwachung des Verbindungsstatus und Server-Zeitstempeln können Sie ein Benutzerpräsenzsystem aufbauen. In diesem System speichert jeder Benutzer Daten an einem Datenbankstandort, um anzuzeigen, ob ein Echtzeitdatenbank-Client online ist oder nicht. Clients setzen diesen Standort auf „true“, wenn sie online gehen, und auf einen Zeitstempel, wenn sie die Verbindung trennen. Dieser Zeitstempel gibt an, wann der angegebene Benutzer das letzte Mal online war.

Beachten Sie, dass Ihre App die Trennvorgänge in die Warteschlange stellen sollte, bevor ein Benutzer als online markiert wird, um Race Conditions für den Fall zu vermeiden, dass die Netzwerkverbindung des Clients unterbrochen wird, bevor beide Befehle an den Server gesendet werden können.

Hier ist ein einfaches Benutzerpräsenzsystem:

Schnell

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections")

// stores the timestamp of my last disconnect (the last time I was seen online)
let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")

let connectedRef = Database.database().reference(withPath: ".info/connected")

connectedRef.observe(.value, with: { snapshot in
  // only handle connection established (or I've reconnected after a loss of connection)
  guard snapshot.value as? Bool ?? false else { return }

  // add this device to my connections list
  let con = myConnectionsRef.childByAutoId()

  // when this device disconnects, remove it.
  con.onDisconnectRemoveValue()

  // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
  // where you set the user's presence to true and the client disconnects before the
  // onDisconnect() operation takes effect, leaving a ghost user.

  // this value could contain info about the device or a timestamp instead of just true
  con.setValue(true)

  // when I disconnect, update the last time I was seen online
  lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
})

Ziel c

Hinweis: Dieses Firebase-Produkt ist auf dem App Clip-Ziel nicht verfügbar.
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
FIRDatabaseReference *myConnectionsRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/connections"];

// stores the timestamp of my last disconnect (the last time I was seen online)
FIRDatabaseReference *lastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];

FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    // connection established (or I've reconnected after a loss of connection)

    // add this device to my connections list
    FIRDatabaseReference *con = [myConnectionsRef childByAutoId];

    // when this device disconnects, remove it
    [con onDisconnectRemoveValue];

    // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
    // where you set the user's presence to true and the client disconnects before the
    // onDisconnect() operation takes effect, leaving a ghost user.

    // this value could contain info about the device or a timestamp instead of just true
    [con setValue:@YES];


    // when I disconnect, update the last time I was seen online
    [lastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
  }
}];