Teste o Cloud Firestore: conheça o banco de dados escalonável e flexível do Firebase e do Google Cloud Platform. Saiba mais sobre o Cloud Firestore.

Ler e gravar dados no iOS

Como ter uma FIRDatabaseReference

Para ler ou gravar dados no banco de dados, você precisa de uma instância de FIRDatabaseReference:

Swift

var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Como ler e gravar dados

Este documento aborda as noções básicas de leitura e gravação de dados do Firebase.

Os dados do Firebase são gravados em uma referência FIRDatabase e recuperados por meio de um listener assíncrono anexado à referência. Ele é acionado uma vez no estado inicial dos dados, e posteriormente quando há alterações.

Operações básicas de gravação

Em operações básicas de gravação, use setValue para salvar dados em uma referência e substitua os dados existentes no caminho. Use esse método para:

  • passar os tipos que correspondam aos tipos JSON disponíveis:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Por exemplo, adicione um usuário com setValue desta forma:

Swift

self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

O setValue substitui os dados no local especificado, incluindo qualquer nó filho. No entanto, ainda é possível atualizar um filho sem substituir o objeto inteiro. Para que os usuários atualizem os próprios perfis, atualize o nome deles desta forma:

Swift

self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Detectar eventos de valor

Para ler os dados em um caminho e detectar as alterações, use os métodos observeEventType:withBlock ou observeSingleEventOfType:withBlock de FIRDatabaseReference para monitorar os eventos FIRDataEventTypeValue.

Tipo de evento Uso normal
FIRDataEventTypeValue Ler e detectar alterações em todo o conteúdo de um caminho.

Use o evento FIRDataEventTypeValue para ler os dados em um caminho específico, exatamente como estão no momento do evento. Esse método será acionado uma vez quando o listener for anexado, e sempre que houver alteração nos dados, inclusive nos filhos. O retorno de chamada do evento recebe um snapshot que contém todos os dados no local, inclusive os dos filhos. Se não houver dados, o instantâneo retornará false quando você chamar exists() e nil quando você ler a propriedade value.

O exemplo a seguir mostra um aplicativo de blog social recuperando detalhes de uma postagem do banco de dados:

Swift

refHandle = postRef.observe(DataEventType.value, with: { (snapshot) in
  let postDict = snapshot.value as? [String : AnyObject] ?? [:]
  // ...
})

Objective-C

_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

No momento do evento, o listener recebe um FIRDataSnapshot que contém os dados no local especificado do banco de dados na propriedade value. É possível atribuir os valores ao tipo nativo apropriado, como NSDictionary. Se não existirem dados no local, o value será nil.

Ler dados uma vez

Em alguns casos, é necessário executar um retorno de chamada apenas uma vez e, depois, removê-lo imediatamente. Por exemplo, quando inicializar um elemento de IU que não deve ser alterado. Nesse caso, use o método observeSingleEventOfType para simplificar este cenário: o retorno de chamada do evento incluído é acionado apenas uma vez.

Isso é útil para dados que só precisam ser carregados uma vez, não são alterados com frequência nem exigem detecção ativa. Por exemplo, o app de blog dos exemplos anteriores usa este método para carregar o perfil de um usuário quando ele começa a redigir uma nova postagem:

Swift

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

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);
}];

Como atualizar ou excluir dados

Atualizar campos específicos

Para gravar simultaneamente filhos específicos de um nó sem substituir outros nós filhos, use o método updateChildValues.

Ao chamar updateChildValues, será possível atualizar valores filhos de níveis inferiores. Para fazer isso, basta especificar um caminho para a chave. Se os dados estiverem armazenados em vários locais para aprimorar a escalabilidade, atualize todas as instâncias usando a distribuição de dados. Por exemplo, um app de blog social pode criar uma postagem e atualizá-la simultaneamente no feed de atividades recentes e no feed do autor da postagem. Para fazer isso, o aplicativo de blog usa estes códigos:

Swift

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

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];

Esse exemplo usa childByAutoId para criar uma postagem no nó que contém postagens de todos os usuários em /posts/$postid e, simultaneamente, recuperar a chave com getKey(). A chave pode ser usada para criar uma segunda entrada em /user-posts/$userid/$postid.

Com esses caminhos, você faz atualizações simultâneas em vários locais da árvore JSON com uma única chamada ao updateChildValues, da mesma forma que esse exemplo cria a nova postagem nos dois locais. Essas atualizações são atômicas: ou todas funcionam ou todas falham.

Adicionar um bloco de conclusão

Para saber quando seus dados foram confirmados, você pode adicionar um bloco de conclusão. setValue e updateChildValues recebem um bloco de conclusão opcional, que será chamado quando a gravação for confirmada no banco de dados. Esse listener pode ser útil para monitorar quais dados foram salvos e quais dados ainda estão sendo sincronizados. Se a chamada não for bem-sucedida, o listener receberá um objeto de erro indicando o motivo da falha.

Swift

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!")
  }
}

Objective-C

[[[_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.");
  }
}];

Excluir dados

A maneira mais simples de exclui-los é chamar removeValue em uma referência ao local dos dados.

Outra maneira de excluir é especificando nil como valor para uma operação de gravação, como setValue ou updateChildValues. Use essa técnica com updateChildValues para excluir vários filhos de uma única chamada de API.

Remover listeners

Os observadores não interrompem automaticamente a sincronização de dados quando você sai de um ViewController. Se um observador não for removido corretamente, ele continuará sincronizando dados com a memória local. Quando ele não for mais necessário, remova-o passando o FIRDatabaseHandle associado para o método removeObserverWithHandle.

Quando um bloco de retorno de chamada é adicionado a uma referência, um FIRDatabaseHandle é devolvido. Esses identificadores podem ser usados para remover esse bloco.

Se vários listeners são adicionados a uma referência de banco de dados, cada um é chamado quando um evento é acionado. Para interromper a sincronização de dados nesse local, é necessário remover todos os observadores do local chamando o método removeAllObservers.

Chamar removeObserverWithHandle ou removeAllObservers em um listener não remove automaticamente os listeners registrados nos nós filhos. É preciso também encontrar essas referências ou identificadores para removê-los.

Salvar dados como transações

Ao trabalhar com dados que podem ser corrompidos por modificações simultâneas, como contadores incrementais, use uma operação de transação. Essa operação aceita dois argumentos: uma função de atualização e um retorno de chamada de conclusão opcional. A função de atualização usa o estado atual dos dados como argumento e retorna o novo estado de acordo com as preferências de gravação.

Por exemplo, os usuários do app de blog social podem adicionar ou remover estrelas de postagens e acompanhar quantas foram recebidas da seguinte maneira:

Swift

ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String : AnyObject], let uid = Auth.auth().currentUser?.uid {
    var stars: Dictionary<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

[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);
  }
}];

A transação impede a contagem incorreta de estrelas se vários usuários adicionarem estrelas à mesma postagem simultaneamente ou se os dados do cliente estiverem desatualizados. Inicialmente, o valor na classe FIRMutableData é o último conhecido pelo cliente para o caminho, ou nil se não há nenhum. O servidor compara o valor inicial com o atual e aceita a transação se eles forem correspondentes. Caso contrário, rejeita a transação. Se a transação é rejeitada, o servidor retorna o valor atual para o cliente, que executa a transação novamente com o valor atualizado. Isso se repetirá até que a transação seja aceita ou até que muitas tentativas sejam realizadas.

Gravar dados off-line

Se um cliente perder a conexão de rede, o app continuará funcionando.

Todos os clientes conectados a um banco de dados do Firebase mantêm a própria versão interna de dados ativos. A gravação deles ocorre primeiro nessa versão local. Depois, o cliente do Firebase sincroniza esses dados com os servidores remotos e com outros clientes de acordo com o modelo “melhor esforço".

Consequentemente, todas as gravações no banco de dados acionam eventos locais, antes de qualquer dado ser gravado no servidor, e o app continua responsivo, independentemente da conectividade ou da latência da rede.

Para que a conectividade seja restabelecida, seu app recebe o conjunto apropriado de eventos, e o cliente faz a sincronização com o estado atual do servidor, sem precisar de um código personalizado.

Próximas etapas

Enviar comentários sobre…

Firebase Realtime Database
Precisa de ajuda? Acesse nossa página de suporte.