Core Data Lightweight Migration — Como usar?
O que é “data migration”?
Migration é o que precisamos fazer quando, por qualquer motivo, nosso database (DB) precisa de alterações — sejam alterações nas entidades, nos atributos ou nos relacionamentos.
No Core Data, para alguns tipos de alterações, temos uma ferramenta muito útil que consegue lidar automaticamente com algumas ações de migração. É sobre essa ferramenta que iremos falar hoje — Lightweight migration.
Antes de irmos para os exemplos, vamos à lista de situações em que o lightweight migration pode resolver nossos problemas:
- Deletar entidades, atributos ou relacionamentos;
- Renomear entidades, atributos ou relacionamentos usando o renamingIdentifier;
- Adicionar um novo atributo como
optional
; - Adicionar um novo atributo como
required
e com um valor padrão; - Alterar um atributo
optional
pararequired
especificando também um valor padrão; - Alterar um atributo
required
paraoptional
; - Alterar a hierarquia de entidades;
- Adicionar uma nova “entidade pai” e mover atributos para cima e pra baixo na hierarquia;
- Alterar um relacionamento de
to-one
parato-many
; - Alterar um relacionamento de
non-ordered to-many
paraordered to-many
(e vice versa)
Vamos à pratica!! (uhull)
Se quiser acompanhar com a mão na massa, baixe a primeira versão do código.
Imagine o seguinte… temos um app que gerencia uma lista de tarefas, guardamos isso num DB com a seguinte entidade:
Situação:
O booleano “completed” não atende às convenções de nomeação de booleanos — Que diz que é necessário ter o prefixo “is” ou “has” nesse tipo de variável, sendo assim, isso merece nossa atenção.
Além disso, nossos usuários gostariam de poder ranquear suas tarefas em níveis de prioridade, então acrescentaremos essa feature ao nosso app também (e ao DB).
Então nos temos duas tarefas à fazer.
Antes de mais nada
A primeira coisa que precisamos fazer se queremos usar o migration é adicionar uma nova versão de modelo ao core data, então vamos ao passo a passo:
- Abra o .xcdatamodeld na raiz do app
- No menu editor, clique em Add Model Version…
- Você pode nomear como quiser, mas aconselho que apenas acrescente “v2_” no inicio, ficando assim”v2_TodoApp” (e faça isso pra todos, então nas próximas “v3_”, “v4_”), na frente do “v” você pode também usar a própria versão do app.
- Agora você possui duas versões de modelo, ainda falta um passo para começarmos a alterar.
- Clique no modelo novo e em seguida selecione-o como current model version
- O checkmark verde mudando de modelo significa que tudo esta pronto para começarmos as alterações (asmudanças nos modelos são registradas na versão selecionada como current)
Mudar os nomes dos atributos
Primeiro exemplo é bem simples, é necessário um único passo: com o v2_ aberto, vá no atributo “completed” e mude o name para “isCompleted”, depois disso, insira o renaming ID dele “completed” — fazendo isso estamos sinalizando para o coreData que o antigo “completed” passa a ser “isCompleted” nessa versão.
Você pode deletar as classes e gerar novamente pelo Editor > Create NSManagedObject Subclass (lembre-se de selecionar a pasta model), ou ir direto no arquivo Todo+CoreDataClass.swift e alterar a variável lá. Também será necessário alterar no resto do app onde aquela variável era usada.
Adicionar um atributo para os níveis de prioridade
Crie um novo arquivo swift chamado TodoPriority.swift e cole o código a seguir dentro dele:
O novo atributo que iremos criar, priorityLevel, será controlado pelo enum em nosso app e isso nos traz um ponto legal para discutirmos, o core data só sabe lidar com certos tipos de dados e o nosso enum não faz parte deles, mas sem pânico, temos pelo menos 2 abordagens para resolver esse detalhe:
ValueTransformers
Um Value transformer é uma classe que “traduz” o seu dado em um outro tipo, um que o Core Data conheça, criar um custom value transformer seria a solução mais difícil para os iniciantes, mas além disso, essa solução não é tão usada (pelo menos nas minhas experiencias), por trazer mais trabalho que o necessário visto que há outra solução mais prática… Vamos à ela.
PropertyAccessors
Os Property Accessors são gerados automaticamente para nossos atributos @NSManaged em tempo de execução, mas podemos assumir o controle e gerar nossos próprios accessors.
Com isso nos podemos fazer duas versões do atributo, uma primitiva usada pelo core data e outra do tipo do enum para ser usada pelo restante do app, iremos seguir nessa abordagem, vamos lá.
- Abra nosso modelo v2_ e adicione um novo atributo chamado “priorityLevelPrimitive” do tipo Integer 16(mesmo tipo do rawValue do nosso enum — poderia ser qualquer um, mas escolhemos esse por ocupar menos espaço), desmarque o
optional
e pode deixar o valor padrão dele como 0. - Bom, vamos agora adicionar nossa variável do tipo do enum para podermos usar no resto do sistema. Abra o arquivo Todo+CoreDataClass.swift vamos fazer neste arquivo porque o core data não o gera de novo a menos que nos deletemos ele. E cole o código abaixo dentro da classe Todo:
Explicando:
//1 — Criamos a variável primitiva do modelo no código e tiramos a visibilidade do sistema sobre ela
//2 — Criamos uma nova variável usando o enum e usamos o get/set para trabalhar em conjunto com a variável primitiva
Sempre precisamos acionar o will/did Access/Change quando estamos lendo/escrevendo em um accessor
Legal né?! Agora seriam necessárias varias mudanças no código da UI e na própria UI para ter o seletor de prioridades, e também mudar o nome "completed" para "isCompleted" na onde estávamos usando… Como o foco do artigo é o Lightweight Migration, não vou mostrar como fazer essas alterações, mas sugiro que baixe a versão final onde essas mudanças já estão feitas😉
Agora, graças ao nosso uso do Lightweight Migration, nossos usuários terão seus dados automaticamente migrados ao instalar a atualização e poderão ranquear suas tarefas em ordem de prioridade. Graças a ferramenta que acabamos de aprender a usar, mudanças mais ou menos simples (e até corriqueiras) como essa tendem a ser bem mais tranquilas pra gente e para nossos usuários. Excelente!
Links e referencias:
https://www.raywenderlich.com/7585-lightweight-migrations-in-core-data-tutorial
https://martiancraft.com/blog/2015/12/nsmanaged/
https://www.kairadiagne.com/2020/01/13/nssecurecoding-and-transformable-properties-in-core-data.html
https://dev.to/michi/tips-on-naming-boolean-variables-cleaner-code-35ig