Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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 Persistenz von Daten, zum Verwalten der Präsenz und zum Umgang mit Latenzen.

Festplatten-Persistenz

Firebase-Apps verarbeiten automatisch temporäre Netzwerkunterbrechungen. Zwischengespeicherte Daten sind offline verfügbar und Firebase sendet alle Schreibvorgänge erneut, wenn die Netzwerkverbindung wiederhergestellt ist.

Wenn Sie die Datenträgerpersistenz aktivieren, schreibt Ihre App die Daten lokal auf das Gerät, damit Ihre App den Status auch im Offline-Modus beibehalten kann, selbst wenn der Benutzer oder das Betriebssystem die App neu startet.

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

Schnell

Database.database().isPersistenceEnabled = true

Ziel c

[FIRDatabase database].persistenceEnabled = YES;

Persistenzverhalten

Durch Aktivieren der Persistenz bleiben alle Daten, die der Firebase Realtime Database-Client im Online-Modus synchronisiert, auf der Festplatte gespeichert und sind offline verfügbar, selbst wenn der Benutzer oder das Betriebssystem die App neu startet. Dies bedeutet, dass Ihre App wie online funktioniert, indem sie die im Cache gespeicherten lokalen Daten verwendet. Listener-Callbacks werden weiterhin für lokale Updates ausgelöst.

Der Firebase Realtime Database-Client führt automatisch eine Warteschlange mit allen Schreibvorgängen, 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 die Verbindung wieder herstellt, werden alle Vorgänge an den Firebase Realtime Database-Server gesendet.

Wenn Ihre App verwendet Firebase - Authentifizierung bleibt die Firebase Realtime Datenbank - Client die Authentifizierung des Benutzers Token über App neu gestartet. Wenn das Authentifizierungstoken abläuft, während Ihre App offline ist, hält der Client Schreibvorgänge an, bis Ihre App den Benutzer erneut authentifiziert, andernfalls können die Schreibvorgänge aufgrund von Sicherheitsregeln fehlschlagen.

Daten frisch halten

Die Firebase Realtime Database synchronisiert und speichert eine lokale Kopie der Daten für aktive Listener. Darüber hinaus können Sie bestimmte Standorte synchron halten.

Schnell

let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Ziel c

FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

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

Schnell

scoresRef.keepSynced(false)

Ziel c

[scoresRef keepSynced:NO];

Standardmäßig werden 10 MB der zuvor synchronisierten Daten zwischengespeichert. Dies sollte für die meisten Anwendungen ausreichen. Wenn der Cache seine konfigurierte Größe überschreitet, löscht die Firebase Realtime Database Daten, die am wenigsten zuletzt verwendet wurden. Synchronisierte Daten werden nicht aus dem Cache gelöscht.

Daten offline abfragen

Die Firebase Realtime Database speichert Daten, die von einer Abfrage zurückgegeben werden, um sie offline zu verwenden. Bei Abfragen, die offline erstellt wurden, funktioniert die Firebase Realtime Database 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 Netzwerkverbindung wieder verfügbar ist, werden die Daten geladen und spiegeln die Abfrage wider.

Dieser Code fragt beispielsweise nach den letzten vier Elementen in einer Firebase Realtime Database of Scores

Schnell

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

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

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

Schnell

scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Ziel c

[[[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 „untergeordnete“ 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 noch 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" 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 „Child Added“- und „Value“-Ereignisse für die App ab.

Offline-Handhabung von Transaktionen

Alle Transaktionen, die ausgeführt werden, während die App offline ist, werden in die Warteschlange gestellt. Sobald die App die Netzwerkverbindung wiederhergestellt hat, werden die Transaktionen an den Realtime Database-Server gesendet.

Präsenz verwalten

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

Firebase Database-Clients bieten einfache Primitive, mit denen Sie in die Datenbank schreiben können, wenn ein Client die Verbindung zu den Firebase Database-Servern trennt. Diese Aktualisierungen erfolgen unabhängig davon, ob der Client die Verbindung sauber trennt oder nicht. Sie können sich also darauf verlassen, dass sie Daten bereinigen, selbst wenn eine Verbindung unterbrochen wird oder ein Client abstürzt. Alle Schreibvorgänge, einschließlich des Setzens, Aktualisierens und Entfernens, können bei einer Trennung ausgeführt werden.

Hier ist ein einfaches Beispiel für Daten beim Abschalten Schreiben durch die Verwendung von onDisconnect primitiv:

Schnell

let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Ziel c

FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

Wie onDisconnect funktioniert

Wenn Sie einen etablieren onDisconnect() Betrieb, lebt der Betrieb auf dem Datenbank - Server Firebase Realtime. 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 Anwendung kann den Rückruf auf dem Schreibvorgang verwenden , um sicherzustellen , onDisconnect korrekt angehängt:

Schnell

presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Ziel c

[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

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

Schnell

presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Ziel c

[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

Verbindungsstatus erkennen

Bei vielen präsenzbezogenen Funktionen ist es hilfreich, wenn Ihre App weiß, ob sie online oder offline ist. Firebase Realtime - Datenbank bietet eine besondere Lage am /.info/connected , die jedes Mal , ist der Firebase Realtime - Datenbank - Client aktualisiert wird , ändert Verbindungszustand. Hier ist ein Beispiel:

Schnell

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

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 zwischen Realtime - Datenbank - Clients nicht synchronisiert ist , da der Wert auf dem Zustand des Kunden abhängig ist. Mit anderen Worten, wenn ein Client liest /.info/connected als falsch, dann ist dies keine Garantie , dass ein separater Client auch falsch gelesen wird.

Umgang mit Latenz

Server-Zeitstempel

Die Firebase Realtime Database-Server bieten einen Mechanismus zum Einfügen von auf dem Server generierten Zeitstempeln als Daten. Diese Funktion, die mit kombinierten onDisconnect , bietet eine einfache Möglichkeit , um zuverlässig Kenntnis von der Zeit zu machen , an dem ein Realtime - Datenbank - Client getrennt:

Schnell

let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Ziel c

FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Uhrversatz

Während firebase.database.ServerValue.TIMESTAMP viel genauer und bevorzugt für die meisten Lese- / Schreiboperationen ist, kann es gelegentlich nützlich sein , die Client-Taktverschiebung in Bezug auf die Firebase Realtime - Datenbank-Servern zu schätzen. Sie können einen Rückruf an die Stelle befestigen /.info/serverTimeOffset den Wert in Millisekunden zu erhalten, dass Firebase Realtime - Datenbank - Clients auf die lokale berichteten Zeit (Epochenzeit in Millisekunden) fügen Sie die Serverzeit zu schätzen. Beachten Sie, dass die Genauigkeit dieses Offsets durch die Netzwerklatenz beeinflusst werden kann und daher in erster Linie nützlich ist, um große (> 1 Sekunde) Diskrepanzen in der Uhrzeit zu erkennen.

Schnell

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

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 Verbindungsstatusüberwachung und Serverzeitstempeln können Sie ein Benutzeranwesenheitssystem aufbauen. In diesem System speichert jeder Benutzer Daten an einem Datenbankspeicherort, um anzuzeigen, ob ein Echtzeitdatenbank-Client online ist oder nicht. Clients setzen diesen Speicherort auf true, wenn sie online gehen, und 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-Bedingungen zu vermeiden, falls die Netzwerkverbindung des Clients unterbrochen wird, bevor beide Befehle an den Server gesendet werden können.

Hier ist ein einfaches Benutzerpräsenzsystem:

Schnell

// 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 let connected = snapshot.value as? Bool, connected 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

// 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]];
  }
}];