Photo by NASA on Unsplash

Swift simplificado: qual a lógica por trás de Core Data?

Felipe Bandeira
Apple Developer Academy | UFPE

--

Pra quem quer trabalhar com apps mais robustos, persistência de dados é um tema que provavelmente se mostrará essencial. E, desenvolvendo em Swift, Core Data é um dos principais modos de persistir dados dentro de um app. A internet está cheia de tutorias sobre como implementar Core Data nas aplicações, mas as explicações sobre a lógica que existe por trás disso não são tão fáceis de compreender.

Nos próximos parágrafos, você entenderá quais são as principais classes que estão envolvidas no uso de Core Data e como elas se relacionam para cumprir o tão sonhado objetivo: persistir os seus dados.

Para isso, faremos uma pequena analogia, comparando o processo de armazenar dados ao de guardar itens de valor dentro de um banco.

Parte 1: O Banco

Imagine que, para poder comemorar uma grande conquista pessoal, resolvemos pegar nossas posses mais importantes no banco. Passamos pelos caixas eletrônicos, batemos na porta do gerente e, depois de colocar a conversa em dia, dizemos que queremos ir até o cofre. Em Core Data, estaríamos nos referiando ao…

NSPersistentContainer: é o grande cofre que guarda informações do nosso app em caráter permanente. Como o nome já diz, o Container é onde as informações persistem, sendo ele o ponto de início de nossa jornada.

Em Core Data, o cofre do banco equivale ao Persistent Container.

Entramos no imenso cofre. Como não somos milionários, não precisamos de um cofre inteiro só pra nós. Em vez disso, guardamos nossos itens em uma única gaveta desse cofre. Essa gaveta se chama…

NSManagedObjectContext: se o Container fosse o cofre de um grande banco, o Context seria como uma gaveta individual. É na nossa gaveta que guardamos as posses de grande valor, como joias, dinheiro ou documentos importantes. Da mesma forma, é no Context que os objetos criados pelo app ficam guardados, e sua principal função é auxiliar a registrar quaisquer mudanças que eles possam sofrer.

Uma gaveta individual do cofre, como a sua (ou, se preferir, um Context como o seu)

Uma vez que a memória (o Container) é carregada, o Context expõe os objetos do seu app como eles foram deixados da última vez e “anota” todas as modificações que se faça sobre cada um. É o Context quem permite que façamos undo/redo, inclusive, mas ele representa apenas uma memória temporária, que se apaga com o desligamento do app a menos que seja salvo em um Container outra vez. E, assim como dentro do grande cofre existem várias gavetas, dentro de um Container vários Contexts podem existir!

Vamos parar por um momento e recapitular. Já sabemos que você tem uma gaveta para guardar objetos de valor, e que ela fica dentro do cofre de um banco. Mas embora ela seja sua, você não pode, por razões óbvias, entrar e sair do cofre na hora que quiser. Para poder acessar a sua gaveta, há no banco um gerente que ajuda com isso. Igualmente, para acessar o Context que guardou seu objeto num Container, você precisa de um:

NSPersistentStoreCoordinator: Essa classe existe, basicamente, para fazer a comunicação entre aquelas duas partes. Tudo isso se resume no quadro abaixo.

Imagem de um grande retângulo representado o Container e um menor representando o Context dentro dele.
Até agora, isso é o que vimos.

(Antes de prosseguirmos, vale um pequeno esclarecimento: quando estamos tratando de Core Data, chamamos as classes de modelo de um app de entidades. Essas são as classes específicas do seu aplicativo: não as que são comuns a todos e foram criadas pela Apple (como ViewController), mas as que só existem no seu app e foram criadas por você.

Imagine que o banco, para evitar fraudes, possui um arquivo com o perfil individual de todos os seus clientes. O template desse perfil é como uma classe (ou, no jargão de Core Data, uma entidade), que contém propriedades como "nome", "cpf" e "endereço". Em Core Data, cada uma dessas propriedades é chamada de atributo. Voltaremos a falar disso em breve, ok?)

Parte 2: Os Itens

Entramos no banco, falamos com o gerente, fomos até o cofre e encontramos nossa gaveta. Agora que estamos com ela aberta, vamos pensar nos itens que você guardou lá dentro. Digamos que, por segurança, querendo evitar golpes, o banco mantenha um registro individual de todos os itens que cada cliente deixa em suas gavetas. Esse registro é o:

NSManagedObjectModel: o Model é como o inventário que o banco fez dos seus itens, descrevendo todos os objetos que foram guardados na sua gaveta do cofre. Na prática, o Model é como uma grande lista que contém as descrições de todas as entidades (ou classes) do seu app.

Dentro da sua gaveta (e descritos no seu Model) estão um punhado de joias, duas ou três barras de ouro e o objeto mais valioso da sua vida: o antigo livro de receitas da família! (Convenhamos que nem só de dinheiro é feita a felicidade do homem). Abrimos a antiga capa de couro e, logo na primeira página, vemos um sumário de todas as receitas já escritas. O bolo de rolo com açucar de Dona Maria, o cuscuz com galinha guisada de Seu Geraldo…

O livro de receitas

No entanto, embora todas elas possuam características em comum, como ter uma lista de ingredientes ou o passo-a-passo do preparo, cada receita exige insumos próprios e um jeito particular de se fazer. Podemos entender cada receita como uma entidade, que deve conter dentro de si uma:

NSEntityDescription: é como uma lista menor com todos os atributos (as propriedades) de uma entidade particular. No caso da nossa receita, os atributos poderiam ser entendidos como os ingredientes necessários. Essa lista diz não apenas quais são as características daquela entidade específica, mas também quais as relações que ela tem possui outras entidades.

(Um exemplo de relação entre entidades seria entre as entidades "cofre" e "gaveta". Um cofre é formado por várias gavetas, assim como várias gavetas formam um cofre. Essas são as relações que as duas entidades mantêm).

Se o EntityDescription lista todas as propriedades, cada uma delas é descrita e representada pela classe:

NSPropertyDescription: é a classe que representa cada propriedade unitária de um atributo. No caso da receita, seria o equivalente às especificações dos ingredientes que vamos usar. As propriedades possuem sempre um nome e um tipo. Todo ingrediente de uma receita, por exemplo, possui um nome e uma medida correta em gramas. Na linguagem de Core Data, dizemos que todo atributo "ingrediente" da entidade "receita" possui uma propriedade "nome: string" e uma propriedade "medidaCorretaEmGramas: double".

Por fim, só falta juntarmos isso tudo. Vários ingredientes formam uma receita, várias receitas formam um livro, e esse livro é um…

NSManagedObject: é a classe que representa um objeto individual criado a partir das descrições de uma entidade. Aqui, porém, vale um aviso! Diferentemente do que ocorre com as outras classes, não é comum vermos "NSManagedObject" escrito no código. O mais comum é que, para representar as entidades desejadas, se criem classes específicas que herdam propriedades de NSManagedObject! A razão disso é que é mais fácil codar mudanças nas classes criadas do que em NSManagedObject em si :)

Todas essas relações estão descritas abaixo.

A implementação de Core Data não costuma ser difícil, e os vários tutoriais disponíveis pela internet mostram isso. No entanto, agora que você entende a lógica por trás da aplicação, implementar será bem mais tranquilo.

Até a próxima!

--

--

Felipe Bandeira
Apple Developer Academy | UFPE

Generalist at heart, curious about most things. Currently focusing on ML/AI but always up for learning something new