Le applicazioni Firebase funzionano anche se la tua app perde temporaneamente la connessione alla rete. Inoltre, Firebase fornisce strumenti per rendere persistenti i dati localmente, nonché per gestire la presenza e la latenza.
Persistenza dei dischi
Le app Firebase gestiscono automaticamente le interruzioni temporanee della rete. I dati memorizzati nella cache sono disponibili offline e Firebase invia nuovamente le eventuali scritture quando la connettività di rete viene ripristinata.
Quando attivi la persistenza sul disco, l'app scrive i dati localmente sul dispositivo in modo da poter 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.
Swift
Database.database().isPersistenceEnabled = true
Objective-C
[FIRDatabase database].persistenceEnabled = YES;
Comportamento di persistenza
Se attivi la persistenza, tutti i dati sincronizzati dal client Firebase Realtime Database in modalità online rimangono su disco e sono disponibili offline, anche quando l'utente o il sistema operativo riavvia l'app. Ciò significa che l'app funziona come farebbe online utilizzando i dati locali archiviati nella cache. I callback dell'ascoltatore continueranno a essere attivati 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, questa coda è permanente anche sul disco, in modo che tutte le operazioni di scrittura siano disponibili quando l'utente o il sistema operativo riavvia l'app. Quando l'app riprende la connettività, tutte le operazioni vengono inviate al server Firebase Realtime Database.
Se la tua app utilizza Firebase Authentication, il client Firebase Realtime Database mantiene il token di autenticazione dell'utente durante i riavvii dell'app. Se il token di autenticazione scade quando l'app è offline, il client mette in pausa le operazioni di scrittura finché l'app non autentica di nuovo l'utente, altrimenti le operazioni di scrittura potrebbero non riuscire a causa delle regole di sicurezza.
Aggiornamento dei dati
Firebase Realtime Database sincronizza e archivia una copia locale dei dati per i listener attivi. Inoltre, puoi mantenere sincronizzate località specifiche.
Swift
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
Objective-C
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 ascoltatori attivi. Puoi disattivare di nuovo la sincronizzazione con la seguente riga di codice.
Swift
scoresRef.keepSynced(false)
Objective-C
[scoresRef keepSynced:NO];
Per impostazione predefinita, vengono memorizzati nella cache 10 MB di dati sincronizzati in precedenza. Dovrebbe essere sufficiente per la maggior parte delle applicazioni. Se la cache supera le dimensioni configurate, il Firebase Realtime Database elimina i dati utilizzati meno di recente. I dati mantenuti sincronizzati non vengono eliminati dalla cache.
Eseguire query sui dati offline
Firebase Realtime Database memorizza i dati restituiti da una query per l'utilizzo quando è offline. Per le query create offline, il Firebase Realtime Database continua a funzionare per i dati caricati in precedenza. Se i dati richiesti non sono stati caricati, Firebase Realtime Database carica i dati dalla cache locale. Quando la connettività di rete è di nuovo disponibile, i dati vengono caricati e riflettono la query.
Ad esempio, questo codice esegue query sugli ultimi quattro elementi di un Firebase Realtime Database di punteggi
Swift
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")") }
Objective-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); }];
Supponiamo che l'utente perda la connessione, passi alla modalità offline e riavvii l'app. Mentre è ancora offline, l'app esegue query sugli ultimi due elementi della stessa località. Questa query restituirà correttamente gli ultimi due elementi perché l'app ha caricato tutti e quattro gli elementi nella query precedente.
Swift
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")") }
Objective-C
[[[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 "child added" per i due dinosauri con il punteggio più alto utilizzando la cache persistente. Tuttavia, non verrà generato un evento "value", poiché l'app non ha mai eseguito la query mentre era online.
Se l'app richiedesse gli ultimi sei elementi in modalità offline, riceverebbe subito gli eventi "elemento aggiunto" per i quattro elementi memorizzati nella cache. Quando il dispositivo torna online, il client Firebase Realtime Database si sincronizza con il server e riceve gli ultimi due eventi "elemento figlio aggiunto" e 'valore" per l'app.
Gestione delle transazioni offline
Eventuali transazioni eseguite quando l'app è offline vengono messe in coda. Una volta che l'app recupera la connettività di rete, le transazioni vengono inviate al server Realtime Database.
Gestione della presenza
Nelle applicazioni in tempo reale è spesso utile rilevare quando i client si connettono e si disconnettono. Ad esempio, potresti contrassegnare un utente come "offline" quando il suo client si disconnette.
I client di Firebase Database forniscono primitive semplici che puoi utilizzare per scrivere nel database quando un client si disconnette dai server di Firebase Database. Questi aggiornamenti si verificano indipendentemente dal fatto che il client si disconnetta correttamente o meno, quindi puoi utilizzarli per ripulire i dati anche se una connessione viene interrotta o un client si arresta in modo anomalo. Tutte le operazioni di scrittura, incluse l'impostazione, l'aggiornamento e la rimozione, possono essere eseguite dopo la disconnessione.
Ecco un semplice esempio di scrittura di dati al momento della disconnessione utilizzando la
primitiva onDisconnect
:
Swift
let presenceRef = Database.database().reference(withPath: "disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnectSetValue("I disconnected!")
Objective-C
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"]; // Write a string when this client loses connection [presenceRef onDisconnectSetValue:@"I disconnected!"];
Come funziona onDisconnect
Quando stabilisci un'operazione onDisconnect()
, questa
si trova sul server Firebase Realtime Database. Il server controlla la sicurezza per assicurarsi che l'utente possa eseguire l'evento di scrittura richiesto e informa la tua app se non è valido. Il server monitora quindi la connessione. Se in qualsiasi momento la connessione scade o viene chiusa attivamente dal client Realtime Database, il server controlla la sicurezza una seconda volta (per assicurarsi che l'operazione sia ancora valida), quindi richiama l'evento.
La tua app può utilizzare il callback sull'operazione di scrittura
per assicurarsi che onDisconnect
sia stato allegato correttamente:
Swift
presenceRef.onDisconnectRemoveValue { error, reference in if let error = error { print("Could not establish onDisconnect event: \(error)") } }
Objective-C
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) { if (error != nil) { NSLog(@"Could not establish onDisconnect event: %@", error); } }];
Un evento onDisconnect
può essere annullato anche chiamando .cancel()
:
Swift
presenceRef.onDisconnectSetValue("I disconnected") // some time later when we change our minds presenceRef.cancelDisconnectOperations()
Objective-C
[presenceRef onDisconnectSetValue:@"I disconnected"]; // some time later when we change our minds [presenceRef cancelDisconnectOperations];
Rilevamento dello stato della connessione in corso...
Per molte funzionalità 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 cambia lo stato di connessione del client Firebase Realtime Database. Ecco un esempio:
Swift
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") } })
Objective-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
è un valore booleano che non viene
sincronizzato tra i client Realtime Database perché il valore dipende
dallo stato del client. In altre parole, se un client legge /.info/connected
come falso, non è garantito che anche un altro client lo legga come falso.
Latenza di elaborazione
Timestamp del server
I server Firebase Realtime Database forniscono un meccanismo per inserire come dati i timestamp generati sul server. Questa funzionalità, combinata con
onDisconnect
, fornisce un modo semplice per prendere nota in modo affidabile del
momento in cui un client Realtime Database si è disconnesso:
Swift
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline") userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
Objective-C
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"]; [userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
Spostamento 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 di Firebase Realtime Database. Puoi collegare un callback alla posizione /.info/serverTimeOffset
per ottenere il valore in millisecondi che i client Firebase Realtime Database aggiungono all'ora registrata locale (ora di epoch in millisecondi) per stimare l'ora del server. Tieni presente che l'accuratezza di questo offset può essere influenzata dalla latenza della rete ed è quindi utile principalmente per rilevare discrepanze elevate (> 1 secondo) nell'ora del sistema.
Swift
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)") } })
Objective-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); }];
App di esempio per la presenza
Combinando le operazioni di disconnessione con il monitoraggio dello stato della connessione e i timestamp del server, puoi creare un sistema di presenza degli utenti. 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 si connettono e su un timestamp quando si disconnettono. Questo timestamp indica l'ultima volta in cui l'utente in questione era online.
Tieni presente che l'app deve mettere in coda le operazioni di disconnessione prima che un utente venga contrassegnato come online, per evitare condizioni di gara 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 dell'utente:
Swift
// 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()) })
Objective-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]]; } }];