Aprendendo Cloud Kit

Samuel Brasileiro
Apple Developer Academy | UFPE
11 min readAug 26, 2020

O API de banco de dados da Apple traz uma dinâmica simples, mas um pouco diferente para os programadores acostumados em programar em outros bancos, como o Firebase, da Google.

Por ser um pouco mais específico, é difícil encontrar materiais para aprender CloudKit, de maneira fácil e objetiva (e em português principalmente). Então decidi montar um tutorial aprofundado para explorar essa aplicação. Bora lá?

O Projeto

O nosso objetivo é desenvolver um app parecido com o twitter (só que muito mais simples), onde você pode adicionar um usuário, que pode escrever e postar tweets.

Clone o projeto base neste link:

Configurações iniciais

Antes de começar a programar o app, precisamos configurar o projeto de forma que seja possível a integração com o CloudKit.

  • Em Signing & Capabilities, você precisa definir o seu Team e modificar o Bundle Identifierpara um novo e único identifier.

Depois disso, você deve adicionar a capability iCloud, na aba superior, para aparecer a nova região abaixo. É necessário que você marque as caixas de Key-value storage e CloudKit. Assim, ao clicar aonde a seta aponta, você já pode adicionar um container especial para esse aplicativo. Lembre-se de clicar no botão de recarregar após criá-lo e de selecionar a caixa desse container.

Pronto, já terminamos de fazer as configurações iniciais no Xcode. Para configurarmos o banco, aperta no botão CloudKit Dashboard para redirecionar ao site do CloudKit.

CloudKit Dashboard

Para ser capaz de salvar informações no iCloud, algumas coisas têm de serem configuradas primeiro. Mas, antes disso, é bom a gente se familiarizar com a interface do dashboard.

Na face da esquerda, nós temos, dispostos em uma lista, todos os Containers que ativamos em algum projeto no Xcode. Já na área de Development temos acesso aos dados que são salvos no banco, às taxas de uso e de usuários ativos, entre outros artifícios.

Nesse tutorial, vamos focar na aba de Schema, onde realizaremos a criação da estrutura das entidades e relacionamentos do banco, que, no CloudKit, são chamadas de Records.

No começo, para mim, foi um pouco complicado entender o conceito de entidades e relacionamentos, mas esse entendimento é essencial para a construção de um bom banco de dados. Para ajudar vocês, se precisar desse contexto básico, explico um pouco nesse texto do Medium:

Agora que já temos noção de como funciona, como seria a estrutura do nosso banco de twitter?

Pensei no seguinte minimundo:

Existem N usuários

Um usuário possui nome

Um usuário pode escrever X tweets

Um tweet pode e deve ser escrito por apenas um usuário

Um tweet possui texto e data de criação

Assim, ficamos com essa modelagem:

Mas como colocamos isso no Dashboard?

Dentro da área Schema aparece uma tela parecida com a de baixo. Para adicionar um novo Record, você deve clicar no botão New Type e digitar o nome da entidade, que no nosso caso é "TwitterUser".

Na área de Custom Fields nós podemos adicionar nossos atributos, que, para TwitterUser, seria apenas "name". Lembre-se de determinar o tipo do atributo sempre! E não se esqueça de salvar as modificações, no canto inferior direito.

Agora que já criamos o primeiro Record, montar o segundo não é nenhum mistério:

Na verdade, tenho certeza que você está curioso com duas coisas:

  1. Cadê o atributo "createdAt" ???

Essa resposta é facil… Não precisa! Um dos atributos de sistema, que existe em toda Record é o createdAt, para facilitar a nossa vida. Além dele, existem vários outros, como modifiedAt, recordName, createdBy, etc…

2. Por que tem um atributo chamado "tweeter"?

É um pouco mais complicado, mas vamos lá! Ao criar a estrutura de um relacionamento, temos que ter bastante cuidado. Eu poderia ter criado um Record específico chamado Write onde possuiria dois atributos: A referência para o Tweet e a referência para o escritor do Tweet. E isso não está errado! Seria a solução ideal se mais de um usuário pudessem ser donos do mesmo tweet, mas não é o caso, então usar essa tabela é repetitivo.

A solução mais prática é a gente colocar a referência ao usuário que escreveu o tweet como um novo atributo em Tweet! Entendeu?

Agora que já esclarecemos tudo, falta configurarmos uma coisinha nos Records: os Indexes. Logo embaixo de Custom Field, que você acabou de preencher, tem um botão escrito Edit Indexes, em azul. Clique nele que você será levado para a seguinte tela:

Aqui se configura os índices dos Records. Os índices servem para quando formos pesquisar e recuperar algum dado no banco de dados para o aplicativo.

No CloudKit, existem três tipos de index:

  1. Queriable — Ele é usado se você deseja consultar um Record por meio de um atributo específico;
  2. Sortable — É usado se você precisa ordenar os resultados de uma busca em base de algum atributo;
  3. Searchable — Quando se faz uma pesquisa usando CONTAINS, quero saber se tal atributo(de tipo string) da entidade contém o termo de pesquisa, para retornar uma lista com todos os objetos que possuem.

Então vamos configurar nossos indexes:

Para Tweet, queremos que recordName seja Queriable, createdAt seja Queriable e Sortable, text seja Queriable, Sortable e Searchable e tweeter seja Queriable.

Para TweetUser, queremos que recordName seja Queriable, createdAt seja Queriable e Sortable e name seja Queriable, Sortable e Searchable.

Não se esqueça de salvar.

Acabamos a parte de configuração, hora de partir para o código, finalmente!

O Código

Lembrando que, como o foco é CloudKit, não vou aprofundar no desenvolvimento de UIKit do projeto, já que se contempla basicamente de UIAlertController e TableViewController :) Vamos lá!

Observações

  • Não esqueça de colocar import CloudKit no começo do arquivo CloudViewController.swift, para usarmos as funções da biblioteca.
  • Para termos acesso ao container que montamos no CloudKit Dashboard, precisamos adicionar let privateDatabase = CKContainer(identifier: NomeDoContainerQueVoceCriou).privateCloudDatabase nas variáveis do controller.

Realizando essas modificações, o começo do arquivo deve ficar desse jeito:

Agora, a primeira coisa que deveríamos aprender seria como escrever no banco de dados. Então vamos começar com a ação acionada quando o botão createNewUserButton é pressionado.

Cadastrando o usuário

Primeiro, precisamos recolher o nome do novo usuário para cadastrá-lo no banco e, para isso, criei uma função getNameAlert(title: String, message: String, action: @escaping (String)->Void), que cria um alerta com um espaço de text field para escrever o nome e que, quando o botão OK for pressionado, realiza-se a ação action.

Obs.: Fiz a criação dessa função desse jeito para, no futuro, podermos reutilizar ela ao pesquisar o nome do usuário para realizar outra função.

Desse jeito, a estrutura inicial da função buttonAction é da seguinte forma:

Para finalizar, só falta desenvolvermos a função que recebe o nome do usuário e cadastra ele na nuvem. Vamos chamar ela de createNewUser, como você já deve ter notado no código acima.

createNewUser

  • Linhas 3–5. Primeiro, antes de cadastrar o usuário no banco temos que considerar o seguinte: não quero que seja possível que dois usuários possuam o mesmo nome de usuário. Assim, a gente precisa fazer uma Query de TwitterUser, uma Pesquisa, no banco, antes de escrever o novo usuário. Especificamos essa pesquisa, usando o NSPredicate, para que todos os resultados da nossa pesquisa possuam o atributo "name" de mesmo valor que a string TweeterName.
  • Linha 8. Para realizar a operação da Query e recuperar seus resultados, precisamos associar ela a uma CKQueryOperation.
  • Linha 10. Essa variável serve para me dizer se existe algum usuário já cadastrado com esse nome no banco.
  • Linha 11. Esse bloco é onde a gente recupera os dados da pesquisa. Ele é executado para todo resultado que aparece. (se existir algum resultado, convertemos existed para true.
  • Linha 18. Quando toda a query já é lida, esse bloco é executado.
  • Linha 19. Importante: Como esses blocos não são síncronos, precisamos abrir um bloco DispatchQueue.main.async para poder realizar alguma operação de UI sem nenhum erro.
  • Linhas 21–29. Se já existir um usuário no banco, crio uma alerta e chamo a função getNameAlert de novo.
  • Linha 31. É o record de tipo "TwitterUser". Essa variável possui a estrutura e os atributos que definimos no CloudKit Dashbord.
  • Linha 32. Nessa função você ESCREVE o valor do atributo dentro do record.
  • Linha 34. Para salvar na nuvem esse Record que criamos localmente, precisamos escrever essa função de Save.
  • Linha 37–47. Aqui é o código que escrevi para lidar com possíveis erros ao salvar (e apresentar ao usuário).
  • Linha 59. Aqui a gente adiciona a operação de Query para começar a ser realizada no nosso banco.

Nesse momento já conseguimos salvar quantos usuários quisermos no banco! Agora bora partir para escrever os tweets?

Escrevendo o Tweet

Primeiro, vamos entender um pouco o que pensei nessa função: A gente escreve o nome do usuário e, se ele existir no database, a gente escreve o tweet!

A gente vai começar poupando trabalho. Lembra que comentei que daria para reutilizar a função getNameAlert? É agora. Vamos criar apenas a função pós escrita do nome! A nossa função buttonAction é pra ficar desse jeito agora:

Agora, a gente vai desenvolver a função WriteTweet, que vai, primeiramente, checar se existe um usuário com o nome que escrevemos e, se existir, partimos pra escrever o tweet.

writeTweet

  • Linhas 2–17. É repetido o que acontece com a função createNewUser, só que, se existir um Record TwitterUser no banco com o atributo "name" igual a TwitterName, então a gente armazena tal Record nessa nova variável user.
  • Linhas 23–31. Se não existir um usuário na query, crio uma alerta e chamo a função getNameAlert de novo.
  • Linhas 34–40. Se existir, apresentamos um novo UIAlertController, que contém um UITextView para a escrita do texto do tweet.
  • Linhas 42–47. Ao pressionar o botão OK é criado um novo Record do tipo Tweet e é escrito no atributo "text" o conteúdo do text view.
  • Linhas 49–50. Lembra que, no CloudKit Dashboard determinamos que o Record Tweet possui também uma variável chamada "tweeter", que é uma referência ao usuário que escreveu o tweet? Então, para escrevermos essa referência, precisamos criar um CKRecord.Reference, que contém o valor do Record que queremos fazer referência (user). Outra coisa importante: action, nesse contexto, significa 'O que devo fazer com esse Tweet, caso esse TwitterUser for apagado do banco?'. .none significa que nada é para acontecer, enquanto .deleteSelf significa que quero que Tweet seja apagado do banco também, um caso de Delete Cascade.
  • Linha 51. Para salvar na nuvem esse Record que criamos localmente, precisamos escrever essa função de Save.

O resto desta função é só a implementação de algumas features de UI, como o UITextView.

Já fizemos muita coisa, não foi? Pelo menos só faltamais um pouco. A gente já consegue cadastrar os usuários e escrever os tweets perfeitamente. Só falta a gente conseguir visualizar isso tudo, né?

Leitura dos dados

Como que vai funcionar essa visualização? Queremos mostrar os tweets dispostos em uma Table View, que seriam organizados pela data de criação mais recente.

Então, o que precisamos fazer primeiro é adicionar duas variáveis para o View Controller que conterão os dados dos tweets e usuários.

  • A Lista tweets armazenará os Records de tweets que serão dispostos na Table View.
  • O Dicionário usersByID armazenará os usuários atráves da sua identificação única recordName.

Vamos criar uma nova função updateConsole() e modificaremos mais uma vez a função buttonAction para o seguinte:

updateConsole

A função vai ter a seguinte estrutura:

  • Linhas 3–4. Toda vez que o botão for pressionado, precisamos limpar a lista e dicionário, para poder recolher tudo de novo.
  • Linha 8. A pesquisa que vamos fazer vai ser uma Query, sem nenhuma limitação (por isso que o valor do predicate é true).
  • Linha 10. Um Sort Descriptor tem a função de descrever de que maneira você quer a organização da Query. No nosso caso, queremos que ela seja organizada apenas pelo atributo creationDate, de mais recente para mais antigo.
  • Linha 14. Esse bloco será executado para cada Record da Query.
  • Linhas 18–19. A gente já tem acesso ao valor do Record de tipo Tweet, então já podemos adicioná-lo na Table View e atualizá-la.
  • Linha 21. Como sabemos que record é do tipo Tweet, sabemos que ele possui a variável de referência tweeter. Mas, lembrando: possuímos a referência apenas. Não temos acesso ainda aos atributos de TwitterUser.
  • Linha 23. Aqui é apenas para otimização: Se já fizemos a leitura do record de um usuário, para quê fazermos de novo?
  • Linha 25. Aqui é onde a gente vai fazer a leitura dos dados do usuário, a partir da referência. Precisamos fazer uma Fetch no banco de dados, para recolher a referência.
  • Linhas 29–30. Agora só falta escrever o usuário no dicionário e atualizar a Table View.

Alterações na Table View

Só falta a gente associar a leitura da lista e do dicionário à Table View. As modificações na Extension serão assim:

  • Linha 4. Precisamos que essa função retorne a quantidade de itens da Table View, que seria equivalente a tweets.count.
  • Linhas 18–20. Aqui a gente vai definir um formato e colocar a data de criação do tweet na label dateLabel da célula.
  • Linha 22. Escrevo na label tweetText o valor do texto do tweet.
  • Linhas 23–28. Simplificando, se os dados do TwitterUser já estiverem disponíveis, atualizamos a label userName da célula

Com essas mudanças, a gente já consegue visualizar perfeitamente todos os tweets no Console!

Apagando o banco

Só falta desenvolver a função de apagar o banco da nuvem. Antes de apagar, solicitaremos a confirmação da ação, com um AlertController. A função buttonAction vai ficar desse jeito (finalmente haha):

Note que, se o botão de apagar for pressionado, a função deleteRecord vai ser executada para apagar todos os dados tanto de Tweet quanto de TwitterUser.

deleteRecord

  • Linha 2. Essa lista serve para armazenar os ids que vamos deletar.
  • Linha 10. Eu adiciono o id de cada item da Query nessa lista.
  • Linha 14–17. Quando terminar de armazenar os ids nessa lista, fazemos a exclusão de cada Record usando a função self.privateDatabase.delete(withRecordID: CKRecord.ID).

Tenha cuidado ao deletar, porque esses dados vão ser apagados definitivamente!

Notas gerais

O que se pode fazer com CloudKit (e banco de dados, em geral) é realmente enorme mas muito massa. Tentei explorar alguns conceitos que tive bastante dificuldade para encontrar e aprender, para ajudar principalmente quem tem dificuldade de ler conteúdos em inglês :)

É bastante coisa para fazer, né? Não fica intimidado com a quantidade de informação que tem, tá ok? Minha sugestão é ir digerindo em partes. Salva esse texto aqui no Medium e não tenha pressa para acompanhá-lo.

Outra coisa que é importante para o CloudKit funcionar no dispositivo é que o usuário esteja conectado ao iCloud no seu dispositivo. Sem isso, o aplicativo não funciona. Quando estiver trabalhando com um app de verdade, tenta fazer a checagem se o usuário está conectado ao iCloud ao abrir o app.

E é isso, gente!

Muito obrigado por ter lido e que ótimo que você deu uma chance ao CloudKit :)

Se precisar de alguma ajuda ou quiser comentar alguma coisa, não tenha vergonha, pode me mandar uma mensagem no instagram ou comentar nesse post!

https://instagram.com/samuelbsantosn/

--

--