获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Leer y escribir datos en plataformas Apple

(Opcional) Realice prototipos y pruebas con Firebase Local Emulator Suite

Antes de hablar sobre cómo su aplicación lee y escribe en Realtime Database, presentemos un conjunto de herramientas que puede usar para crear prototipos y probar la funcionalidad de Realtime Database: Firebase Local Emulator Suite. Si está probando diferentes modelos de datos, optimizando sus reglas de seguridad o trabajando para encontrar la forma más rentable de interactuar con el back-end, poder trabajar localmente sin implementar servicios en vivo puede ser una gran idea.

Un emulador de Realtime Database es parte de Local Emulator Suite, que permite que su aplicación interactúe con el contenido y la configuración de su base de datos emulada, así como, opcionalmente, con los recursos de su proyecto emulado (funciones, otras bases de datos y reglas de seguridad).

Usar el emulador de Realtime Database implica solo unos pocos pasos:

  1. Agregar una línea de código a la configuración de prueba de su aplicación para conectarse al emulador.
  2. Desde la raíz del directorio de tu proyecto local, ejecuta firebase emulators:start .
  3. Hacer llamadas desde el código prototipo de su aplicación usando un SDK de la plataforma Realtime Database como de costumbre, o usando la API REST de Realtime Database.

Se encuentra disponible un tutorial detallado que involucra Realtime Database y Cloud Functions . También debería echar un vistazo a la introducción de Local Emulator Suite .

Obtener una referencia de base de datos FIR

Para leer o escribir datos de la base de datos, necesita una instancia de FIRDatabaseReference :

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
var ref: DatabaseReference!

ref = Database.database().reference()

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
@property (strong, nonatomic) FIRDatabaseReference *ref;

self.ref = [[FIRDatabase database] reference];

Escribir datos

Este documento cubre los aspectos básicos de la lectura y escritura de datos de Firebase.

Los datos de Firebase se escriben en una referencia de la Database de datos y se recuperan adjuntando un oyente asíncrono a la referencia. El detector se activa una vez para el estado inicial de los datos y nuevamente cada vez que los datos cambian.

Operaciones básicas de escritura

Para operaciones básicas de escritura, puede usar setValue para guardar datos en una referencia específica, reemplazando cualquier dato existente en esa ruta. Puede utilizar este método para:

  • Pase los tipos que correspondan a los tipos JSON disponibles de la siguiente manera:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Por ejemplo, puede agregar un usuario con setValue de la siguiente manera:

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
self.ref.child("users").child(user.uid).setValue(["username": username])

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

El uso de setValue de esta manera sobrescribe los datos en la ubicación especificada, incluidos los nodos secundarios. Sin embargo, aún puede actualizar un elemento secundario sin volver a escribir todo el objeto. Si desea permitir que los usuarios actualicen sus perfiles, puede actualizar el nombre de usuario de la siguiente manera:

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
self.ref.child("users/\(user.uid)/username").setValue(username)

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Leer datos

Leer datos escuchando eventos de valor

Para leer datos en una ruta y escuchar los cambios, use observeEventType:withBlock de FIRDatabaseReference para observar eventos FIRDataEventTypeValue .

Tipo de evento Uso típico
FIRDataEventTypeValue Lea y escuche los cambios en todo el contenido de una ruta.

Puede usar el evento FIRDataEventTypeValue para leer los datos en una ruta determinada, tal como existen en el momento del evento. Este método se activa una vez cuando se adjunta el oyente y nuevamente cada vez que cambian los datos, incluidos los elementos secundarios. La devolución de llamada del evento recibe una snapshot que contiene todos los datos en esa ubicación, incluidos los datos secundarios. Si no hay datos, la instantánea devolverá false cuando llame a exists() y nil cuando lea su propiedad de value .

El siguiente ejemplo muestra una aplicación de blogs sociales que recupera los detalles de una publicación de la base de datos:

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

El agente de escucha recibe una FIRDataSnapshot que contiene los datos en la ubicación especificada en la base de datos en el momento del evento en su propiedad de value . Puede asignar los valores al tipo nativo adecuado, como NSDictionary . Si no existen datos en la ubicación, el value es nil .

Leer datos una vez

Leer una vez usando getData()

El SDK está diseñado para administrar las interacciones con los servidores de la base de datos, ya sea que su aplicación esté en línea o fuera de línea.

En general, debe usar las técnicas de eventos de valor descritas anteriormente para leer datos y recibir notificaciones de actualizaciones de datos desde el backend. Esas técnicas reducen su uso y facturación, y están optimizadas para brindarles a sus usuarios la mejor experiencia mientras se conectan y desconectan.

Si necesita los datos solo una vez, puede usar getData() para obtener una instantánea de los datos de la base de datos. Si por alguna razón getData() no puede devolver el valor del servidor, el cliente sondeará el caché de almacenamiento local y devolverá un error si aún no se encuentra el valor.

El siguiente ejemplo demuestra cómo recuperar el nombre de usuario público de un usuario una sola vez desde la base de datos:

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
ref.child("users/\(uid)/username").getData(completion:  { error, snapshot in
  guard error == nil else {
    print(error!.localizedDescription)
    return;
  }
  let userName = snapshot.value as? String ?? "Unknown";
});

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid];
[[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) {
  if (error) {
    NSLog(@"Received an error %@", error);
    return;
  }
  NSString *userName = snapshot.value;
}];

El uso innecesario de getData() puede aumentar el uso del ancho de banda y provocar una pérdida de rendimiento, lo que se puede evitar mediante el uso de un oyente en tiempo real como se muestra arriba.

Leer datos una vez con un observador

En algunos casos, es posible que desee que el valor de la memoria caché local se devuelva inmediatamente, en lugar de buscar un valor actualizado en el servidor. En esos casos, puede usar observeSingleEventOfType para obtener los datos de la memoria caché del disco local de inmediato.

Esto es útil para los datos que solo deben cargarse una vez y no se espera que cambien con frecuencia ni requieran una escucha activa. Por ejemplo, la aplicación de blogs de los ejemplos anteriores usa este método para cargar el perfil de un usuario cuando comienza a crear una nueva publicación:

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in
  // Get user value
  let value = snapshot.value as? NSDictionary
  let username = value?["username"] as? String ?? ""
  let user = User(username: username)

  // ...
}) { error in
  print(error.localizedDescription)
}

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
NSString *userID = [FIRAuth auth].currentUser.uid;
[[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  // Get user value
  User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]];

  // ...
} withCancelBlock:^(NSError * _Nonnull error) {
  NSLog(@"%@", error.localizedDescription);
}];

Actualizar o eliminar datos

Actualizar campos específicos

Para escribir simultáneamente en elementos secundarios específicos de un nodo sin sobrescribir otros nodos secundarios, utilice el método updateChildValues .

Al llamar a updateChildValues , puede actualizar los valores secundarios de nivel inferior especificando una ruta para la clave. Si los datos se almacenan en varias ubicaciones para escalar mejor, puede actualizar todas las instancias de esos datos mediante distribución de datos . Por ejemplo, una aplicación de blogs sociales puede querer crear una publicación y actualizarla simultáneamente con la fuente de actividad reciente y la fuente de actividad del usuario que publica. Para ello, la aplicación de blogs utiliza un código como este:

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
guard let key = ref.child("posts").childByAutoId().key else { return }
let post = ["uid": userID,
            "author": username,
            "title": title,
            "body": body]
let childUpdates = ["/posts/\(key)": post,
                    "/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
NSString *key = [[_ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"uid": userID,
                       @"author": username,
                       @"title": title,
                       @"body": body};
NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post,
                               [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post};
[_ref updateChildValues:childUpdates];

Este ejemplo usa childByAutoId para crear una publicación en el nodo que contiene publicaciones para todos los usuarios en /posts/$postid y recuperar simultáneamente la clave con getKey() . Luego, la clave se puede usar para crear una segunda entrada en las publicaciones del usuario en /user-posts/$userid/$postid .

Con estas rutas, puede realizar actualizaciones simultáneas en varias ubicaciones en el árbol JSON con una sola llamada a updateChildValues , como en este ejemplo, se crea la nueva publicación en ambas ubicaciones. Las actualizaciones simultáneas realizadas de esta manera son atómicas: todas las actualizaciones se realizan correctamente o todas fallan.

Agregar un bloque de finalización

Si desea saber cuándo se confirmaron sus datos, puede agregar un bloque de finalización. Tanto setValue como updateChildValues ​​toman un bloque de finalización opcional que se llama cuando la escritura se ha confirmado en la base de datos. Este oyente puede ser útil para realizar un seguimiento de qué datos se han guardado y qué datos aún se están sincronizando. Si la llamada no tuvo éxito, se le pasa al oyente un objeto de error que indica por qué ocurrió la falla.

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
ref.child("users").child(user.uid).setValue(["username": username]) {
  (error:Error?, ref:DatabaseReference) in
  if let error = error {
    print("Data could not be saved: \(error).")
  } else {
    print("Data saved successfully!")
  }
}

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  if (error) {
    NSLog(@"Data could not be saved: %@", error);
  } else {
    NSLog(@"Data saved successfully.");
  }
}];

Borrar datos

La forma más sencilla de eliminar datos es llamar a removeValue en una referencia a la ubicación de esos datos.

También puede eliminar especificando nil como valor para otra operación de escritura, como setValue o updateChildValues . Puede usar esta técnica con updateChildValues ​​para eliminar varios elementos secundarios en una sola llamada a la API.

Separar oyentes

Los observadores no dejan de sincronizar datos automáticamente cuando dejas un ViewController . Si un observador no se elimina correctamente, continúa sincronizando datos en la memoria local. Cuando ya no se necesite un observador, elimínelo pasando el FIRDatabaseHandle asociado al método removeObserverWithHandle .

Cuando agrega un bloque de devolución de llamada a una referencia, se devuelve un FIRDatabaseHandle . Estos identificadores se pueden utilizar para eliminar el bloqueo de devolución de llamada.

Si se han agregado varios detectores a una referencia de base de datos, se llama a cada detector cuando se genera un evento. Para detener la sincronización de datos en esa ubicación, debe eliminar todos los observadores en una ubicación llamando al método removeAllObservers .

Llamar removeObserverWithHandle o removeAllObservers en un agente de escucha no elimina automáticamente los agentes de escucha registrados en sus nodos secundarios; también debe realizar un seguimiento de esas referencias o identificadores para eliminarlos.

Guardar datos como transacciones

Cuando trabaje con datos que podrían corromperse por modificaciones simultáneas, como contadores incrementales, puede usar una operación de transacción . Le da a esta operación dos argumentos: una función de actualización y una devolución de llamada de finalización opcional. La función de actualización toma el estado actual de los datos como argumento y devuelve el nuevo estado deseado que le gustaría escribir.

Por ejemplo, en la aplicación de blogs sociales de ejemplo, puede permitir que los usuarios destaquen y no destaquen publicaciones y realicen un seguimiento de cuántas estrellas ha recibido una publicación de la siguiente manera:

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String: AnyObject],
    let uid = Auth.auth().currentUser?.uid {
    var stars: [String: Bool]
    stars = post["stars"] as? [String: Bool] ?? [:]
    var starCount = post["starCount"] as? Int ?? 0
    if let _ = stars[uid] {
      // Unstar the post and remove self from stars
      starCount -= 1
      stars.removeValue(forKey: uid)
    } else {
      // Star the post and add self to stars
      starCount += 1
      stars[uid] = true
    }
    post["starCount"] = starCount as AnyObject?
    post["stars"] = stars as AnyObject?

    // Set value and report transaction success
    currentData.value = post

    return TransactionResult.success(withValue: currentData)
  }
  return TransactionResult.success(withValue: currentData)
}) { error, committed, snapshot in
  if let error = error {
    print(error.localizedDescription)
  }
}

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
  NSMutableDictionary *post = currentData.value;
  if (!post || [post isEqual:[NSNull null]]) {
    return [FIRTransactionResult successWithValue:currentData];
  }

  NSMutableDictionary *stars = post[@"stars"];
  if (!stars) {
    stars = [[NSMutableDictionary alloc] initWithCapacity:1];
  }
  NSString *uid = [FIRAuth auth].currentUser.uid;
  int starCount = [post[@"starCount"] intValue];
  if (stars[uid]) {
    // Unstar the post and remove self from stars
    starCount--;
    [stars removeObjectForKey:uid];
  } else {
    // Star the post and add self to stars
    starCount++;
    stars[uid] = @YES;
  }
  post[@"stars"] = stars;
  post[@"starCount"] = @(starCount);

  // Set value and report transaction success
  currentData.value = post;
  return [FIRTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError * _Nullable error,
                       BOOL committed,
                       FIRDataSnapshot * _Nullable snapshot) {
  // Transaction completed
  if (error) {
    NSLog(@"%@", error.localizedDescription);
  }
}];

El uso de una transacción evita que los recuentos de estrellas sean incorrectos si varios usuarios protagonizan la misma publicación al mismo tiempo o si el cliente tenía datos obsoletos. El valor contenido en la clase FIRMutableData es inicialmente el último valor conocido del cliente para la ruta, o nil si no hay ninguno. El servidor compara el valor inicial con su valor actual y acepta la transacción si los valores coinciden o la rechaza. Si se rechaza la transacción, el servidor devuelve el valor actual al cliente, que vuelve a ejecutar la transacción con el valor actualizado. Esto se repite hasta que se acepta la transacción o se han realizado demasiados intentos.

Incrementos atómicos del lado del servidor

En el caso de uso anterior, estamos escribiendo dos valores en la base de datos: la ID del usuario que destaca/quita la estrella de la publicación y el número de estrellas incrementado. Si ya sabemos que el usuario está protagonizando la publicación, podemos usar una operación de incremento atómico en lugar de una transacción.

Rápido

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
let updates = [
  "posts/\(postID)/stars/\(userID)": true,
  "posts/\(postID)/starCount": ServerValue.increment(1),
  "user-posts/\(postID)/stars/\(userID)": true,
  "user-posts/\(postID)/starCount": ServerValue.increment(1)
] as [String : Any]
Database.database().reference().updateChildValues(updates);

C objetivo

Nota: Este producto de Firebase no está disponible en el objetivo App Clip.
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1],
                        [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]};
[[[FIRDatabase database] reference] updateChildValues:updates];

Este código no utiliza una operación de transacción, por lo que no se vuelve a ejecutar automáticamente si hay una actualización conflictiva. Sin embargo, dado que la operación de incremento ocurre directamente en el servidor de la base de datos, no hay posibilidad de conflicto.

Si desea detectar y rechazar conflictos específicos de la aplicación, como un usuario que destaca una publicación que ya ha destacado anteriormente, debe escribir reglas de seguridad personalizadas para ese caso de uso.

Trabajar con datos sin conexión

Si un cliente pierde su conexión de red, su aplicación seguirá funcionando correctamente.

Cada cliente conectado a una base de datos de Firebase mantiene su propia versión interna de los datos activos. Cuando se escriben datos, primero se escriben en esta versión local. Luego, el cliente de Firebase sincroniza esos datos con los servidores de base de datos remotos y con otros clientes en base al "mejor esfuerzo".

Como resultado, todas las escrituras en la base de datos desencadenan eventos locales inmediatamente, antes de que se escriba ningún dato en el servidor. Esto significa que su aplicación sigue respondiendo independientemente de la latencia o la conectividad de la red.

Una vez que se restablece la conectividad, su aplicación recibe el conjunto de eventos adecuado para que el cliente se sincronice con el estado actual del servidor, sin tener que escribir ningún código personalizado.

Hablaremos más sobre el comportamiento fuera de línea en Obtenga más información sobre las capacidades en línea y fuera de línea .

Próximos pasos