Funzionalità offline su piattaforme Apple

Le applicazioni Firebase funzionano anche se la tua app perde temporaneamente la connessione di rete. Inoltre, Firebase fornisce strumenti per la persistenza dei dati in locale, la gestione della presenza e la gestione della latenza.

Persistenza del disco

Le app Firebase gestiscono automaticamente le interruzioni temporanee della rete. I dati memorizzati nella cache sono disponibili offline e Firebase invia nuovamente le scritture quando viene ripristinata la connettività di rete.

Quando abiliti la persistenza del disco, l'app scrive i dati in locale sul dispositivo in modo che l'app possa mantenere lo stato offline, anche se l'utente o il sistema operativo riavvia l'app.

Puoi abilitare la persistenza del disco con una sola riga di codice.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
Database.database().isPersistenceEnabled = true

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[FIRDatabase database].persistenceEnabled = YES;

Comportamento di persistenza

Abilitando la persistenza, tutti i dati che il client Firebase Realtime Database sincronizzerebbe mentre è online persistono su disco e sono disponibili offline, anche quando l'utente o il sistema operativo riavvia l'app. Ciò significa che la tua app funziona come farebbe online utilizzando i dati locali archiviati nella cache. Le richiamate degli ascoltatori continueranno a attivarsi per gli aggiornamenti locali.

Il client Firebase Realtime Database mantiene automaticamente una coda di tutte le operazioni di scrittura eseguite mentre l'app è offline. Quando la persistenza è abilitata, anche questa coda viene mantenuta su disco in modo che tutte le tue scritture siano disponibili quando l'utente o il sistema operativo riavvia l'app. Quando l'app riacquista la connettività, tutte le operazioni vengono inviate al server Firebase Realtime Database.

Se la tua app utilizza l' autenticazione Firebase , il client Firebase Realtime Database mantiene il token di autenticazione dell'utente durante i riavvii dell'app. Se il token di autenticazione scade mentre l'app è offline, il client sospende le operazioni di scrittura finché l'app non autentica nuovamente l'utente, altrimenti le operazioni di scrittura potrebbero non riuscire a causa delle regole di sicurezza.

Mantenere i dati aggiornati

Il database Firebase Realtime sincronizza e archivia una copia locale dei dati per gli ascoltatori attivi. Inoltre, puoi mantenere sincronizzate posizioni specifiche.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

Il client Firebase Realtime Database scarica automaticamente i dati in queste posizioni e li mantiene sincronizzati anche se il riferimento non ha listener attivi. È possibile disattivare la sincronizzazione con la seguente riga di codice.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
scoresRef.keepSynced(false)

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[scoresRef keepSynced:NO];

Per impostazione predefinita, vengono memorizzati nella cache 10 MB di dati precedentemente sincronizzati. Questo dovrebbe essere sufficiente per la maggior parte delle applicazioni. Se la cache supera le dimensioni configurate, Firebase Realtime Database elimina i dati che sono stati utilizzati meno di recente. I dati mantenuti sincronizzati non vengono eliminati dalla cache.

Interrogazione dei dati offline

Il database Firebase Realtime archivia i dati restituiti da una query per l'utilizzo offline. Per le query costruite offline, il database Firebase Realtime continua a funzionare per i dati caricati in precedenza. Se i dati richiesti non sono stati caricati, il database Firebase Realtime carica i dati dalla cache locale. Quando la connettività di rete è nuovamente disponibile, i dati vengono caricati e rifletteranno la query.

Ad esempio, questo codice esegue una query per gli ultimi quattro elementi in un database di punteggi in tempo reale di Firebase

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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")")
}

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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);
    }];

Si supponga che l'utente perda la connessione, vada offline e riavvii l'app. Mentre è ancora offline, l'app interroga gli ultimi due elementi dalla stessa posizione. Questa query restituirà correttamente gli ultimi due elementi perché l'app ha caricato tutti e quattro gli elementi nella query precedente.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

Nell'esempio precedente, il client Firebase Realtime Database genera eventi "figli aggiunti" per i due dinosauri con il punteggio più alto, utilizzando la cache persistente. Ma non genererà un evento di "valore", poiché l'app non ha mai eseguito quella query mentre è online.

Se l'app richiedesse gli ultimi sei elementi mentre è offline, otterrebbe immediatamente eventi "aggiunti figlio" per i quattro elementi memorizzati nella cache. Quando il dispositivo torna online, il client Firebase Realtime Database si sincronizza con il server e ottiene gli ultimi due eventi "figli aggiunti" e "valore" per l'app.

Gestione delle transazioni offline

Tutte le transazioni eseguite mentre l'app è offline vengono accodate. Una volta che l'app ha riacquistato la connettività di rete, le transazioni vengono inviate al server del database in tempo reale.

Gestire la presenza

Nelle applicazioni in tempo reale è spesso utile rilevare quando i client si connettono e si disconnettono. Ad esempio, potresti voler contrassegnare un utente come "offline" quando il suo client si disconnette.

I client del database Firebase forniscono semplici primitive che puoi utilizzare per scrivere nel database quando un client si disconnette dai server del database Firebase. Questi aggiornamenti si verificano indipendentemente dal fatto che il client si disconnette in modo pulito o meno, quindi puoi fare affidamento su di essi per ripulire i dati anche se una connessione viene interrotta o un client si arresta in modo anomalo. Tutte le operazioni di scrittura, inclusi l'impostazione, l'aggiornamento e la rimozione, possono essere eseguite in caso di disconnessione.

Ecco un semplice esempio di scrittura di dati dopo la disconnessione utilizzando la primitiva onDisconnect :

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

Come funziona onDisconnect

Quando si stabilisce un'operazione onDisconnect() , l'operazione risiede sul server Firebase Realtime Database. Il server verifica la sicurezza per assicurarsi che l'utente possa eseguire l'evento di scrittura richiesto e informa la tua app se non è valida. Il server quindi controlla la connessione. Se in qualsiasi momento la connessione va in timeout o viene chiusa attivamente dal client Realtime Database, il server verifica la sicurezza una seconda volta (per assicurarsi che l'operazione sia ancora valida) e quindi richiama l'evento.

L'app può utilizzare la richiamata durante l'operazione di scrittura per assicurarsi che onDisconnect sia stato collegato correttamente:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

Un evento onDisconnect può anche essere annullato chiamando .cancel() :

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

Rilevamento dello stato della connessione

Per molte funzioni relative alla presenza, è utile che la tua app sappia quando è online o offline. Firebase Realtime Database fornisce una posizione speciale in /.info/connected che viene aggiornata ogni volta che lo stato di connessione del client Firebase Realtime Database cambia. Ecco un esempio:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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")
  }
})

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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 è un valore booleano che non è sincronizzato tra i client Realtime Database perché il valore dipende dallo stato del client. In altre parole, se un client legge /.info/connected come false, ciò non garantisce che anche un client separato leggerà false.

Gestione della latenza

Timestamp del server

I server Firebase Realtime Database forniscono un meccanismo per inserire i timestamp generati sul server come dati. Questa funzionalità, combinata con onDisconnect , fornisce un modo semplice per prendere nota in modo affidabile dell'ora in cui un client di Realtime Database si è disconnesso:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Oscillazione dell'orologio

Sebbene firebase.database.ServerValue.TIMESTAMP sia molto più accurato e preferibile per la maggior parte delle operazioni di lettura/scrittura, a volte può essere utile stimare lo sfasamento dell'orologio del client rispetto ai server del database Firebase Realtime. È possibile allegare una richiamata alla posizione /.info/serverTimeOffset per ottenere il valore, in millisecondi, che i client Firebase Realtime Database aggiungono all'ora locale riportata (tempo di epoca in millisecondi) per stimare l'ora del server. Si noti che la precisione di questo offset può essere influenzata dalla latenza di rete e quindi è utile principalmente per rilevare discrepanze di grandi dimensioni (> 1 secondo) nell'ora.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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)")
  }
})

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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);
}];

Esempio di app di presenza

Combinando le operazioni di disconnessione con il monitoraggio dello stato della connessione e i timestamp del server, è possibile creare un sistema di presenza dell'utente. In questo sistema, ogni utente memorizza i dati in una posizione del database per indicare se un client Realtime Database è online o meno. I client impostano questa posizione su true quando sono online e su un timestamp quando si disconnettono. Questo timestamp indica l'ultima volta che l'utente specificato è stato online.

Tieni presente che la tua app deve mettere in coda le operazioni di disconnessione prima che un utente venga contrassegnato online, per evitare condizioni di competizione nel caso in cui la connessione di rete del client venga persa prima che entrambi i comandi possano essere inviati al server.

Ecco un semplice sistema di presenza utente:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
// 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())
})

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
// 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]];
  }
}];
,

Le applicazioni Firebase funzionano anche se la tua app perde temporaneamente la connessione di rete. Inoltre, Firebase fornisce strumenti per la persistenza dei dati in locale, la gestione della presenza e la gestione della latenza.

Persistenza del disco

Le app Firebase gestiscono automaticamente le interruzioni temporanee della rete. I dati memorizzati nella cache sono disponibili offline e Firebase invia nuovamente le scritture quando viene ripristinata la connettività di rete.

Quando abiliti la persistenza del disco, l'app scrive i dati in locale sul dispositivo in modo che l'app possa mantenere lo stato offline, anche se l'utente o il sistema operativo riavvia l'app.

Puoi abilitare la persistenza del disco con una sola riga di codice.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
Database.database().isPersistenceEnabled = true

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[FIRDatabase database].persistenceEnabled = YES;

Comportamento di persistenza

Abilitando la persistenza, tutti i dati che il client Firebase Realtime Database sincronizzerebbe mentre è online persistono su disco e sono disponibili offline, anche quando l'utente o il sistema operativo riavvia l'app. Ciò significa che la tua app funziona come farebbe online utilizzando i dati locali archiviati nella cache. Le richiamate degli ascoltatori continueranno a attivarsi per gli aggiornamenti locali.

Il client Firebase Realtime Database mantiene automaticamente una coda di tutte le operazioni di scrittura eseguite mentre l'app è offline. Quando la persistenza è abilitata, anche questa coda viene mantenuta su disco in modo che tutte le tue scritture siano disponibili quando l'utente o il sistema operativo riavvia l'app. Quando l'app riacquista la connettività, tutte le operazioni vengono inviate al server Firebase Realtime Database.

Se la tua app utilizza l' autenticazione Firebase , il client Firebase Realtime Database mantiene il token di autenticazione dell'utente durante i riavvii dell'app. Se il token di autenticazione scade mentre l'app è offline, il client sospende le operazioni di scrittura finché l'app non autentica nuovamente l'utente, altrimenti le operazioni di scrittura potrebbero non riuscire a causa delle regole di sicurezza.

Mantenere i dati aggiornati

Il database Firebase Realtime sincronizza e archivia una copia locale dei dati per gli ascoltatori attivi. Inoltre, puoi mantenere sincronizzate posizioni specifiche.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

Il client Firebase Realtime Database scarica automaticamente i dati in queste posizioni e li mantiene sincronizzati anche se il riferimento non ha listener attivi. È possibile disattivare la sincronizzazione con la seguente riga di codice.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
scoresRef.keepSynced(false)

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[scoresRef keepSynced:NO];

Per impostazione predefinita, vengono memorizzati nella cache 10 MB di dati precedentemente sincronizzati. Questo dovrebbe essere sufficiente per la maggior parte delle applicazioni. Se la cache supera le dimensioni configurate, Firebase Realtime Database elimina i dati che sono stati utilizzati meno di recente. I dati mantenuti sincronizzati non vengono eliminati dalla cache.

Interrogazione dei dati offline

Il database Firebase Realtime archivia i dati restituiti da una query per l'utilizzo offline. Per le query costruite offline, il database Firebase Realtime continua a funzionare per i dati caricati in precedenza. Se i dati richiesti non sono stati caricati, il database Firebase Realtime carica i dati dalla cache locale. Quando la connettività di rete è nuovamente disponibile, i dati vengono caricati e rifletteranno la query.

Ad esempio, questo codice esegue una query per gli ultimi quattro elementi in un database di punteggi in tempo reale di Firebase

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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")")
}

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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);
    }];

Si supponga che l'utente perda la connessione, vada offline e riavvii l'app. Mentre è ancora offline, l'app interroga gli ultimi due elementi dalla stessa posizione. Questa query restituirà correttamente gli ultimi due elementi perché l'app ha caricato tutti e quattro gli elementi nella query precedente.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

Nell'esempio precedente, il client Firebase Realtime Database genera eventi "figli aggiunti" per i due dinosauri con il punteggio più alto, utilizzando la cache persistente. Ma non genererà un evento di "valore", poiché l'app non ha mai eseguito quella query mentre è online.

Se l'app richiedesse gli ultimi sei elementi mentre è offline, otterrebbe immediatamente eventi "aggiunti figlio" per i quattro elementi memorizzati nella cache. Quando il dispositivo torna online, il client Firebase Realtime Database si sincronizza con il server e ottiene gli ultimi due eventi "figli aggiunti" e "valore" per l'app.

Gestione delle transazioni offline

Tutte le transazioni eseguite mentre l'app è offline vengono accodate. Una volta che l'app ha riacquistato la connettività di rete, le transazioni vengono inviate al server del database in tempo reale.

Gestire la presenza

Nelle applicazioni in tempo reale è spesso utile rilevare quando i client si connettono e si disconnettono. Ad esempio, potresti voler contrassegnare un utente come "offline" quando il suo client si disconnette.

I client del database Firebase forniscono semplici primitive che puoi utilizzare per scrivere nel database quando un client si disconnette dai server del database Firebase. Questi aggiornamenti si verificano indipendentemente dal fatto che il client si disconnette in modo pulito o meno, quindi puoi fare affidamento su di essi per ripulire i dati anche se una connessione viene interrotta o un client si arresta in modo anomalo. Tutte le operazioni di scrittura, inclusi l'impostazione, l'aggiornamento e la rimozione, possono essere eseguite in caso di disconnessione.

Ecco un semplice esempio di scrittura di dati dopo la disconnessione utilizzando la primitiva onDisconnect :

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

Come funziona onDisconnect

Quando si stabilisce un'operazione onDisconnect() , l'operazione risiede sul server Firebase Realtime Database. Il server verifica la sicurezza per assicurarsi che l'utente possa eseguire l'evento di scrittura richiesto e informa la tua app se non è valida. Il server quindi controlla la connessione. Se in qualsiasi momento la connessione va in timeout o viene chiusa attivamente dal client Realtime Database, il server verifica la sicurezza una seconda volta (per assicurarsi che l'operazione sia ancora valida) e quindi richiama l'evento.

L'app può utilizzare la richiamata durante l'operazione di scrittura per assicurarsi che onDisconnect sia stato collegato correttamente:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

Un evento onDisconnect può anche essere annullato chiamando .cancel() :

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

Rilevamento dello stato della connessione

Per molte funzioni relative alla presenza, è utile che la tua app sappia quando è online o offline. Firebase Realtime Database fornisce una posizione speciale in /.info/connected che viene aggiornata ogni volta che lo stato di connessione del client Firebase Realtime Database cambia. Ecco un esempio:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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")
  }
})

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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 è un valore booleano che non è sincronizzato tra i client Realtime Database perché il valore dipende dallo stato del client. In altre parole, se un client legge /.info/connected come false, ciò non garantisce che anche un client separato leggerà false.

Gestione della latenza

Timestamp del server

I server Firebase Realtime Database forniscono un meccanismo per inserire i timestamp generati sul server come dati. Questa funzionalità, combinata con onDisconnect , fornisce un modo semplice per prendere nota in modo affidabile dell'ora in cui un client di Realtime Database si è disconnesso:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Oscillazione dell'orologio

Sebbene firebase.database.ServerValue.TIMESTAMP sia molto più accurato e preferibile per la maggior parte delle operazioni di lettura/scrittura, a volte può essere utile stimare lo sfasamento dell'orologio del client rispetto ai server del database Firebase Realtime. È possibile allegare una richiamata alla posizione /.info/serverTimeOffset per ottenere il valore, in millisecondi, che i client Firebase Realtime Database aggiungono all'ora locale riportata (tempo di epoca in millisecondi) per stimare l'ora del server. Si noti che la precisione di questo offset può essere influenzata dalla latenza di rete e quindi è utile principalmente per rilevare grandi (> 1 secondo) discrepanze nell'ora.

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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)")
  }
})

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
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);
}];

Esempio di app di presenza

Combinando le operazioni di disconnessione con il monitoraggio dello stato della connessione e i timestamp del server, è possibile creare un sistema di presenza dell'utente. In questo sistema, ogni utente memorizza i dati in una posizione del database per indicare se un client Realtime Database è online o meno. I client impostano questa posizione su true quando sono online e su un timestamp quando si disconnettono. Questo timestamp indica l'ultima volta che l'utente specificato è stato online.

Tieni presente che la tua app deve mettere in coda le operazioni di disconnessione prima che un utente venga contrassegnato online, per evitare condizioni di competizione nel caso in cui la connessione di rete del client venga persa prima che entrambi i comandi possano essere inviati al server.

Ecco un semplice sistema di presenza utente:

Veloce

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
// 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())
})

Obiettivo-C

Nota: questo prodotto Firebase non è disponibile nella destinazione App Clip.
// 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]];
  }
}];