Capacidades sin conexión en plataformas Apple

Las aplicaciones de Firebase funcionan incluso si su aplicación pierde temporalmente su conexión de red. Además, Firebase proporciona herramientas para conservar datos localmente, administrar la presencia y controlar la latencia.

Persistencia del disco

Las aplicaciones de Firebase manejan automáticamente las interrupciones temporales de la red. Los datos almacenados en caché están disponibles sin conexión y Firebase reenvía cualquier escritura cuando se restablece la conectividad de la red.

Cuando habilita la persistencia del disco, su aplicación escribe los datos localmente en el dispositivo para que pueda mantener el estado mientras está fuera de línea, incluso si el usuario o el sistema operativo reinicia la aplicación.

Puede habilitar la persistencia del disco con solo una línea de código.

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
Database.database().isPersistenceEnabled = true

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
[FIRDatabase database].persistenceEnabled = YES;

Comportamiento de persistencia

Al habilitar la persistencia, cualquier dato que el cliente Firebase Realtime Database sincronice mientras está en línea persiste en el disco y está disponible sin conexión, incluso cuando el usuario o el sistema operativo reinicia la aplicación. Esto significa que su aplicación funciona como lo haría en línea utilizando los datos locales almacenados en el caché. Las devoluciones de llamadas de los oyentes seguirán enviándose para obtener actualizaciones locales.

El cliente Firebase Realtime Database mantiene automáticamente una cola de todas las operaciones de escritura que se realizan mientras su aplicación está sin conexión. Cuando la persistencia está habilitada, esta cola también persiste en el disco para que todas sus escrituras estén disponibles cuando el usuario o el sistema operativo reinicie la aplicación. Cuando la aplicación recupera la conectividad, todas las operaciones se envían al servidor de Firebase Realtime Database.

Si su aplicación usa Firebase Authentication , el cliente Firebase Realtime Database conserva el token de autenticación del usuario durante los reinicios de la aplicación. Si el token de autenticación caduca mientras su aplicación está fuera de línea, el cliente pausa las operaciones de escritura hasta que su aplicación vuelva a autenticar al usuario; de lo contrario, las operaciones de escritura podrían fallar debido a reglas de seguridad.

Mantener los datos actualizados

Firebase Realtime Database sincroniza y almacena una copia local de los datos para los oyentes activos. Además, puede mantener sincronizadas ubicaciones específicas.

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

El cliente Firebase Realtime Database descarga automáticamente los datos en estas ubicaciones y los mantiene sincronizados incluso si la referencia no tiene oyentes activos. Puede desactivar la sincronización con la siguiente línea de código.

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
scoresRef.keepSynced(false)

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
[scoresRef keepSynced:NO];

De forma predeterminada, se almacenan en caché 10 MB de datos previamente sincronizados. Esto debería ser suficiente para la mayoría de las aplicaciones. Si el caché supera su tamaño configurado, Firebase Realtime Database purga los datos que se han utilizado menos recientemente. Los datos que se mantienen sincronizados no se eliminan de la caché.

Consulta de datos sin conexión

Firebase Realtime Database almacena los datos devueltos por una consulta para usarlos sin conexión. Para consultas creadas sin conexión, Firebase Realtime Database continúa funcionando para los datos cargados previamente. Si los datos solicitados no se han cargado, Firebase Realtime Database carga datos desde el caché local. Cuando la conectividad de red vuelva a estar disponible, los datos se cargarán y reflejarán la consulta.

Por ejemplo, este código consulta los últimos cuatro elementos en una base de datos de puntuaciones en tiempo real de Firebase.

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
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")")
}

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
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);
    }];

Supongamos que el usuario pierde la conexión, se desconecta y reinicia la aplicación. Mientras aún está fuera de línea, la aplicación consulta los dos últimos elementos desde la misma ubicación. Esta consulta devolverá correctamente los dos últimos elementos porque la aplicación había cargado los cuatro elementos en la consulta anterior.

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

En el ejemplo anterior, el cliente Firebase Realtime Database genera eventos de "niño agregado" para los dos dinosaurios con la puntuación más alta, mediante el uso del caché persistente. Pero no generará un evento de "valor", ya que la aplicación nunca ejecutó esa consulta mientras estaba en línea.

Si la aplicación solicitara los últimos seis elementos sin conexión, obtendría eventos de "niño agregado" para los cuatro elementos almacenados en caché de inmediato. Cuando el dispositivo vuelve a estar en línea, el cliente Firebase Realtime Database se sincroniza con el servidor y obtiene los dos últimos eventos 'niño agregado' y 'valor' para la aplicación.

Manejo de transacciones sin conexión

Todas las transacciones que se realizan mientras la aplicación está fuera de línea se ponen en cola. Una vez que la aplicación recupera la conectividad de la red, las transacciones se envían al servidor de Realtime Database.

Gestionar la presencia

En aplicaciones en tiempo real, suele resultar útil detectar cuándo los clientes se conectan y desconectan. Por ejemplo, es posible que desees marcar a un usuario como "fuera de línea" cuando su cliente se desconecta.

Los clientes de Firebase Database proporcionan primitivas simples que puedes usar para escribir en la base de datos cuando un cliente se desconecta de los servidores de Firebase Database. Estas actualizaciones ocurren independientemente de que el cliente se desconecte limpiamente o no, por lo que puede confiar en ellas para limpiar los datos incluso si se interrumpe una conexión o un cliente falla. Todas las operaciones de escritura, incluida la configuración, actualización y eliminación, se pueden realizar tras una desconexión.

A continuación se muestra un ejemplo sencillo de escritura de datos tras una desconexión utilizando la primitiva onDisconnect :

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

Cómo funciona onDisconnect

Cuando estableces una operación onDisconnect() , la operación reside en el servidor de Firebase Realtime Database. El servidor verifica la seguridad para asegurarse de que el usuario pueda realizar el evento de escritura solicitado e informa a su aplicación si no es válido. Luego, el servidor monitorea la conexión. Si en algún momento la conexión se agota o el cliente Realtime Database la cierra activamente, el servidor verifica la seguridad por segunda vez (para asegurarse de que la operación aún sea válida) y luego invoca el evento.

Su aplicación puede usar la devolución de llamada en la operación de escritura para garantizar que onDisconnect se adjuntó correctamente:

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

Un evento onDisconnect también se puede cancelar llamando .cancel() :

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

Detectando el estado de la conexión

Para muchas funciones relacionadas con la presencia, es útil que su aplicación sepa cuándo está en línea o fuera de línea. Firebase Realtime Database proporciona una ubicación especial en /.info/connected que se actualiza cada vez que cambia el estado de conexión del cliente de Firebase Realtime Database. Aquí hay un ejemplo:

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
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")
  }
})

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
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 es un valor booleano que no está sincronizado entre clientes de Realtime Database porque el valor depende del estado del cliente. En otras palabras, si un cliente lee /.info/connected como falso, esto no garantiza que otro cliente también lea falso.

Manejo de la latencia

Marcas de tiempo del servidor

Los servidores de Firebase Realtime Database proporcionan un mecanismo para insertar marcas de tiempo generadas en el servidor como datos. Esta característica, combinada con onDisconnect , proporciona una manera fácil de tomar nota de manera confiable del momento en que un cliente de Realtime Database se desconectó:

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Desviación del reloj

Si bien firebase.database.ServerValue.TIMESTAMP es mucho más preciso y preferible para la mayoría de las operaciones de lectura/escritura, en ocasiones puede resultar útil para estimar la desviación del reloj del cliente con respecto a los servidores de Firebase Realtime Database. Puedes adjuntar una devolución de llamada a la ubicación /.info/serverTimeOffset para obtener el valor, en milisegundos, que los clientes de Firebase Realtime Database agregan al tiempo reportado local (tiempo de época en milisegundos) para estimar el tiempo del servidor. Tenga en cuenta que la precisión de esta compensación puede verse afectada por la latencia de la red y, por lo tanto, es útil principalmente para descubrir grandes discrepancias (> 1 segundo) en el tiempo del reloj.

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
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)")
  }
})

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
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);
}];

Aplicación de presencia de muestra

Al combinar operaciones de desconexión con monitoreo del estado de la conexión y marcas de tiempo del servidor, puede crear un sistema de presencia de usuarios. En este sistema, cada usuario almacena datos en una ubicación de base de datos para indicar si un cliente de Realtime Database está en línea o no. Los clientes configuran esta ubicación como verdadera cuando se conectan y una marca de tiempo cuando se desconectan. Esta marca de tiempo indica la última vez que el usuario determinado estuvo en línea.

Tenga en cuenta que su aplicación debe poner en cola las operaciones de desconexión antes de que un usuario sea marcado en línea, para evitar condiciones de carrera en caso de que la conexión de red del cliente se pierda antes de que ambos comandos puedan enviarse al servidor.

A continuación se muestra un sistema sencillo de presencia de usuarios:

Rápido

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
// 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())
})

C objetivo

Nota: Este producto de Firebase no está disponible en el destino del clip de aplicación.
// 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]];
  }
}];