Lettura e scrittura di dati sulle piattaforme Apple

(Facoltativo) Prototipazione e test con Firebase Local Emulator Suite

Prima di parlare di come la tua app legge e scrive in Realtime Database, introduciamo un insieme di strumenti che puoi utilizzare per realizzare prototipi e testare la funzionalità di Realtime Database: Firebase Local Emulator Suite. Se stai provando dati diversi di machine learning, ottimizzando le regole di sicurezza o lavorando per trovare economicamente conveniente per interagire con il backend, potendo lavorare localmente senza il deployment di servizi live può essere un'ottima idea.

Un emulatore Realtime Database fa parte di Local Emulator Suite, che consente alla tua app di interagire con i contenuti e la configurazione del database emulati, nonché, facoltativamente, con le risorse del progetto emulate (funzioni, altri database e regole di sicurezza).

L'utilizzo dell'emulatore Realtime Database prevede solo pochi passaggi:

  1. Aggiunta di una riga di codice alla configurazione di test dell'app per connettersi all'emulatore.
  2. Dalla radice della directory del progetto locale, in esecuzione di firebase emulators:start.
  3. Effettuare chiamate dal codice prototipo della tua app utilizzando una piattaforma Realtime Database come di consueto o utilizzando l'API REST Realtime Database.

È disponibile una procedura dettagliata che coinvolge Realtime Database e Cloud Functions. Consulta anche l'introduzione a Local Emulator Suite.

recupera un FIRDatabaseReference

Per leggere o scrivere dati dal database, devi avere un'istanza di FIRDatabaseReference:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Scrittura di dati

Questo documento illustra le nozioni di base per leggere e scrivere i dati di Firebase.

I dati di Firebase vengono scritti in un riferimento Database e recuperati collegando un ascoltatore asincrono al riferimento. L'ascoltatore viene attivato una volta per lo stato iniziale dei dati e di nuovo ogni volta che i dati cambiano.

Operazioni di scrittura di base

Per le operazioni di scrittura di base, puoi utilizzare setValue per salvare i dati in un sostituendo eventuali dati esistenti in quel percorso. Puoi utilizzare questo metodo per:

  • Tipi di tessere corrispondenti ai tipi JSON disponibili:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Ad esempio, puoi aggiungere un utente con setValue nel seguente modo:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

L'utilizzo di setValue in questo modo sovrascrive i dati nella posizione specificata, inclusi eventuali nodi figlio. Tuttavia, puoi comunque aggiornare un elemento secondario senza riscrivere l'intero oggetto. Se vuoi consentire agli utenti di aggiornare i propri profili puoi aggiornare il nome utente nel seguente modo:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Lettura di dati

Leggere i dati ascoltando gli eventi di valore

Per leggere i dati in un percorso e rimanere in ascolto delle modifiche, utilizza la classe observeEventType:withBlock di FIRDatabaseReference da osservare FIRDataEventTypeValue eventi.

Tipo di evento Utilizzo tipico
FIRDataEventTypeValue Lettura e ascolto delle modifiche all'intero contenuto di un percorso.

Puoi utilizzare l'evento FIRDataEventTypeValue per leggere i dati in un determinato percorso, così come sono al momento dell'evento. Questo metodo viene attivato una volta quando viene collegato e ogni volta che i dati, inclusi eventuali elementi figlio, modifiche. Il callback dell'evento viene trasmesso come snapshot contenente tutti i dati a quel punto posizione, inclusi i dati dei bambini. Se non esistono dati, lo snapshot restituirà false quando chiami exists() e nil quando leggi la relativa proprietà value.

L'esempio seguente mostra un'applicazione di social blogging che recupera i dettagli di un post dal database:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

Il listener riceve un FIRDataSnapshot che contiene i dati nel posizione nel database al momento dell'evento nella relativa proprietà value. Tu può assegnare i valori al tipo nativo appropriato, come NSDictionary. Se non esistono dati nella posizione, value è nil.

I dati vengono letti una volta

Leggi una volta utilizzando getData()

L'SDK è progettato per gestire le interazioni con i server di database, l'app sia online o offline.

In genere, devi utilizzare le tecniche degli eventi relativi ai valori descritte sopra per leggere i dati e ricevere notifiche degli aggiornamenti dei dati dal backend. Queste tecniche riducono l'utilizzo e la fatturazione e sono ottimizzate per offrire ai tuoi utenti la migliore esperienza quando sono online e offline.

Se hai bisogno dei dati una sola volta, puoi utilizzare getData() per ottenere uno snapshot dei dal database. Se per qualsiasi motivo getData() non è in grado di restituire il valore del server, il client esaminerà la cache dello spazio di archiviazione locale e restituirà un errore se il valore non viene ancora trovato.

L'esempio seguente mostra il recupero del nome utente visibile al pubblico di un utente una sola volta dal database:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'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;
}];

L'uso non necessario di getData() può aumentare l'uso della larghezza di banda e causare una perdita del rendimento, che può essere evitato utilizzando un listener in tempo reale come mostrato in alto.

Leggere i dati una volta con un osservatore

In alcuni casi potresti voler restituire il valore della cache locale immediatamente, invece di cercare un valore aggiornato sul server. In quelle casi, puoi utilizzare observeSingleEventOfType per ottenere i dati cache su disco locale immediatamente.

È utile per i dati che devono essere caricati una sola volta e non è previsto che cambiano frequentemente o richiedono un ascolto attivo. Ad esempio, l'app di blogging negli esempi precedenti utilizza questo metodo per caricare il profilo di un utente inizia a creare un nuovo post:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'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)
}

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'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);
}];

Aggiornamento o eliminazione dei dati

Aggiorna campi specifici

scrivere contemporaneamente su specifici elementi figlio di un nodo senza sovrascrivere altri nodi figlio, usa il metodo updateChildValues.

Quando chiami updateChildValues, puoi aggiornare i valori secondari di livello inferiore specificando un percorso per la chiave. Se i dati sono archiviati in più località per scalare puoi aggiornare tutte le istanze dei dati utilizzando fan-out dei dati. Ad esempio, un un'app di social blogging potrebbe voler creare un post e aggiornarlo contemporaneamente il feed di attività recenti e il feed di attività dell'utente che ha pubblicato il post. A tale scopo, l'applicazione di blogging utilizza codice come questo:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'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)

Objective-C

Nota: questo prodotto Firebase non è disponibile come target di 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];

Questo esempio utilizza childByAutoId per creare un post nel nodo contenente post per tutti gli utenti in /posts/$postid e recuperare contemporaneamente la chiave con getKey(). La chiave può essere utilizzata per creare una seconda voce nei post dell'utente su /user-posts/$userid/$postid.

Utilizzando questi percorsi, puoi eseguire aggiornamenti simultanei a più località in la struttura JSON con una singola chiamata a updateChildValues, come in questo esempio crea il nuovo post in entrambe le posizioni. Aggiornamenti simultanei realizzati in questo modo sono atomici: tutti gli aggiornamenti hanno esito positivo o tutti gli aggiornamenti non sono riusciti.

Aggiungi un blocco di completamento

Se vuoi sapere quando è stato eseguito il commit dei dati, puoi aggiungere una o un blocco di completamento. Sia setValue che updateChildValues ricevono una di completamento, che viene chiamato quando viene eseguito il commit della scrittura per configurare un database. Questo listener può essere utile per tenere traccia dei dati salvati e quali dati sono ancora in fase di sincronizzazione. Se la chiamata non è andata a buon fine, al listener viene passato un oggetto di errore che indica il motivo dell'errore.

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'app clip.
do {
  try await ref.child("users").child(user.uid).setValue(["username": username])
  print("Data saved successfully!")
} catch {
  print("Data could not be saved: \(error).")
}

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'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.");
  }
}];

Elimina dati

Il modo più semplice per eliminare i dati è chiamare removeValue su un riferimento alla loro posizione.

Puoi anche eseguire l'eliminazione specificando nil come valore per un'altra scrittura come setValue o updateChildValues. Puoi usare questa tecnica con updateChildValues per eliminare più elementi secondari in una singola chiamata API.

Scollega listener

Gli osservatori non interrompono automaticamente la sincronizzazione dei dati quando esci da una ViewController. Se un osservatore non viene rimosso correttamente, la sincronizzazione continua i dati nella memoria locale. Quando un osservatore non è più necessario, rimuovilo passando FIRDatabaseHandle associato al metodo removeObserverWithHandle.

Quando aggiungi un blocco di callback a un riferimento, viene restituito un FIRDatabaseHandle. Questi handle possono essere utilizzati per rimuovere il blocco di callback.

Se sono stati aggiunti più listener a un riferimento di database, ogni listener viene quando viene generato un evento. Per interrompere la sincronizzazione dei dati in quella posizione, devi rimuovere tutti gli osservatori in una posizione chiamando il metodo removeAllObservers.

La chiamata a removeObserverWithHandle o removeAllObservers per un listener non rimuove automaticamente i listener registrati sui relativi nodi figlio; devi inoltre tenere traccia di questi riferimenti o handle per rimuoverli.

Salva i dati come transazioni

Quando si lavora con dati che potrebbero essere danneggiati da istanze modifiche, come i contatori incrementali, puoi utilizzare operazione di transazione. Devi assegnare a questa operazione due argomenti: una funzione di aggiornamento e un di completamento. La funzione di aggiornamento prende lo stato attuale dei dati come argomento e restituisce il nuovo stato desiderato che vuoi scrivere.

Ad esempio, nell'app di social blogging di esempio, puoi consentire agli utenti di aggiungi a Speciali e rimuovi i post da Speciali e tieni traccia del numero di stelle che ha ricevuto come segue:

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'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)
  }
}

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'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);
  }
}];

L'utilizzo di una transazione impedisce che il conteggio delle stelle sia errato se Gli utenti aggiungono a Speciali lo stesso post nello stesso momento oppure il cliente ha dati non aggiornati. La contenuto nella classe FIRMutableData è inizialmente l'ultimo valore valore noto per il percorso o nil in caso contrario. Il server confronta valore iniziale rispetto al valore corrente e accetta la transazione se corrispondono o la rifiuta. Se la transazione viene rifiutata, il server restituisce il valore corrente al client, che esegue di nuovo la transazione con il valore aggiornato. L'operazione si ripete finché la transazione non viene accettata o viene ripetuta tentativi effettuati.

Incrementi lato server atomici

Nel caso d'uso precedente stiamo scrivendo due valori nel database: l'ID di l'utente che aggiunge/rimuovi da Speciali il post e il numero di stelle incrementato. Se sai già che l'utente ha aggiunto il post a Speciali, possiamo usare un incremento atomico anziché una transazione.

Swift

Nota: questo prodotto Firebase non è disponibile nel target dell'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)

Objective-C

Nota: questo prodotto Firebase non è disponibile nel target dell'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];

Questo codice non utilizza un'operazione di transazione, quindi non riceve automaticamente esegui nuovamente se è presente un aggiornamento in conflitto. Tuttavia, poiché l'operazione di incremento avviene direttamente sul server del database, non c'è rischio che si verifichino conflitti.

Se vuoi rilevare e rifiutare conflitti specifici delle applicazioni, ad esempio un utente aggiungere a Speciali un post che ha già aggiunto a Speciali, occorre scrivere regole di sicurezza per quel caso d'uso.

Utilizza i dati offline

Se un client perde la connessione di rete, la tua app continua a funzionare in modo corretto.

Ogni client connesso a un database Firebase mantiene la propria versione interna dei dati attivi. Quando i dati vengono scritti, vengono scritti in questa versione locale per prima cosa. Il client Firebase sincronizza quindi i dati con il database remoto server e con altri clienti nel "best effort" base.

Di conseguenza, tutte le scritture nel database attivano gli eventi locali immediatamente, prima i dati vengono scritti sul server. Ciò significa che la tua app rimane reattivo, indipendentemente dalla latenza o dalla connettività di rete.

Una volta ristabilita la connettività, la tua app riceve l'insieme appropriato di in modo che il client si sincronizzi con lo stato attuale del server, senza dover scrivere codice personalizzato.

Approfondiremo il comportamento offline nel Scopri di più sulle funzionalità online e offline.

Passaggi successivi