10 princípios básicos para usar CoreData sem soprar a cabeça Outubro 17, 2021 | Sem comentários ou 10 batalha lições aprendidas CoreDataCoreData é um framework de gerenciamento de gráficos de objetosCoreData pode armazenar os dados em diferentes formatosUm par de termos definiçãoUse NSPersistentContainerUse relationshipsEvite código com strings tanto quanto possívelConteúdo NSPersitentContainer.viewContext somente leituraVeja essas threadsUtilize o argumento de lançamento para seguir o contexto e threadingUse consultas agregadasKeep core data related code in categoriesAdote um modelo de transação para suas mudanças nos seus dadosNão salve o contexto de dentro de uma categoria de modelo método ou 10 batalha lições aprendidas CoreData Olivier Destrebecq Seguir Maio 1, 2020 – 7 min ler Tenho usado CoreData em vários projetos durante a minha carreira de desenvolvedor de plataformas Apple (a primeira vez foi no MacOS 10.5…), e ao longo dos anos aprendi as coisas da maneira mais difícil (aka: “batendo a minha cabeça contra a parede por muito tempo”). Por isso pensei em partilhar o meu aprendizado com vocês. Espero que eu guarde alguns hematomas na sua testa. Note que este não é um guia de como configurar o CoreData. Ele pretende ser um artigo com alguma boa prática que você pode pegar para seus projetos. Aqui está a visão rápida da Apple: “Use Core Data para salvar os dados permanentes da sua aplicação para uso offline, para armazenar dados temporários e para adicionar funcionalidades de desfazer à sua aplicação em um único dispositivo” Para dar um pouco mais de detalhes, CoreData é a tecnologia da Apple para salvar seus dados estruturados localmente. Você usa o editor de modelos para definir sua estrutura de dados assim como um editor de esquemas de banco de dados, então você pode usar esses objetos em seu código para armazenar seus dados. CoreData é um framework de gerenciamento de gráficos de objetos O que isso significa é que quando você cria seu modelo, você define seus objetos e suas relações. Por exemplo, se você tiver uma entidade Empresa e uma entidade Empregado no seu modelo de dados, essas duas entidades serão ligadas por uma relação que liga essas duas. Provavelmente denominada employees na entidade Company e employer na entidade Employee. Você simplesmente adiciona a propriedade da relação nas entidades e pronto. Você não precisa criar uma tabela de join como em um banco de dados. A beleza do CoreData comparado a um banco de dados tradicional é que quando você consulta para obter a Empresa X, você poderá obter os funcionários diretamente simplesmente acessando company.employees. Não há necessidade de construir uma consulta de join ou de requery. CoreData pode armazenar os dados em diferentes formatos Yes, você ouviu bem, CoreData pode salvar seus dados em diferentes formatos/locais. Cada um tem seus prós e contras. Binary store SQLite Store In Memory store Você pode ler este artigo para obter os prós e contras de cada um. Eu sempre uso SQLite, mas eu poderia ver como uma in-memory store seria super rápida. Um par de termos definição NSManagedObject: esta é a classe base representando os objetos que você tem armazenados no CoreData. Requisição de busca: este é o equivalente a uma consulta no mundo do CoreData. É representada pela classe.NSFetchedObject class. Use NSPersistentContainer Up até iOS 10.0, configurando a pilha CoreData envolvia um pouco de código boilerplate. Você teve que se defender para definir a estratégia a ser usada para lidar com o uso multithreaded do CoreData. Com NSPersistentContainer, a Apple encapsula a maior parte do código da placa de caldeira. Salvando o debate sobre como melhor configurar sua pilha para uma performance ótima. Este artigo contém todas as informações necessárias para usá-la corretamente. Use relationships Eu sempre menciono isto porque em duas ocasiões, para projetos que eu herdei, o modelo CoreData foi configurado como uma base de dados. Nenhuma relação foi definida. Ao invés disso, o ID do objeto relacionado foi armazenado e assim uma requisição extra de fetch foi necessária para obter o objeto relacionado. Eu acho que muitos desenvolvedores abordam o CoreData como um banco de dados e cometem este erro. Isto acaba tornando todo o projeto muito mais complexo e muito mais lento. Seguir este artigo para começar. Evite código com strings tanto quanto possível Código com strings refere-se a código que usa strings para acessar dados. No iOS, você tem algumas APIs com strings: UserDefaults, CoreData são algumas que vêm à mente. Se voltarmos ao exemplo da empresa, abaixo estão duas opções para criar uma empresa e definir seu nome: //stringly version to create and edit a companylet company = NSEntityDescription.insertNewObject(forEntityName: "Company", in: managedObjectContext)company.set("my less than great corporation", forKey: "name")//non stringly versionlet company = Company(in: managedObjectContext)company.name = "My super corporation" No primeiro blush, ambas as opções funcionam, elas compilam, não há nada de errado com elas… O problema com o código com strings é que o compilador não pode ajudá-lo. Imagine que você precisa acessar o name da empresa em vários lugares no código, e realisticamente você precisará de pelo menos dois: um para configurá-lo e outro para lê-lo, agora você tem que digitar aquele name string em dois lugares. Claro que você pode criar uma variável para armazenar a string e usar essa variável, mas isso requer esforço extra, ela não será verificada e todos nós sabemos que os desenvolvedores são preguiçosos…. Então isso normalmente deixa essa string em vários lugares. Então você quer renomeá-la e você tem que caçá-la com a busca e substituí-la… Se você perder uma, seu aplicativo irá travar. Aqui está a lista de características do compilador das quais você não se beneficiará com a sintaxe da string: auto-completar refactor compiler warning in case of misspelling compiler error if you remove/renamed this entity or property in your model To be able to be able to use the non-versão em cadeia do código que você simplesmente precisa pedir ao compilador para gerar classes para suas entidades (você pode seguir as instruções da Apple em “Selecione uma opção de geração de código”). Este é agora o padrão ao criar uma nova Entidade. Conteúdo NSPersitentContainer.viewContext somente leitura Por razões de performance, você não quer executar operações longas no viewContext pois ele será executado na thread principal e potencialmente bloqueará a UI. A solução é adquirir o hábito de apenas fazer modificações nos seus objetos dentro de um bloco que você agende com o método NSPersistentContainer.performBackgroundTask(). Quando você salvar suas alterações nesse bloco, elas serão fundidas novamente em NSPersitentContainer.viewContext e você poderá atualizar sua UI. Veja essas threads Um dos principais drags com CoreData está usando-o de uma maneira segura para threads. Embora uma vez que você tenha obtido o conceito, é bastante fácil. O que se resume é que qualquer objeto de dados que você obtém do CoreData só pode ser usado na thread à qual o objeto managedobjectContext está anexado. Uma maneira de garantir que você está usando a thread correta para acessar seu objeto é usar o NSManagedObjectContext.performBlockAndWait que irá garantir que seu código seja usado na thread correta. Após aquele bloco, você terá que usar NSManagedObject.objectID e NSManagedObjectContext.existingObject(withId:) para passar objetos através de threads e contexto. Se você não fizer isso, provavelmente acabará com deadlocks. Imagine: Vocês programam uma tarefa a partir da thread principal com No caso em que algo programado a partir da thread principal NSManagedObjectContext.performBlockAndWait(). Neste ponto, a thread principal é bloqueada à espera que este bloco seja completado. Incluindo este bloco, você passa o objeto foo do contexto principal para o método bar() e o despacha para outra thread. Para ser um bom cidadão CoreData bar() terá o contexto anexado ao foo e despachará sua tarefa usando foo.managedObjectContext.performBlockAndWait() Boom, você está bloqueado já que este contexto só executa na thread principal e a thread principal está esperando por esta completa. Outra convenção que ajuda a passar objectos entre controladores de visualização apenas se estes existirem no contexto da thread principal. Utilize o argumento de lançamento para seguir o contexto e threading Se seguir as dicas acima, isto deverá limitar as hipóteses de uma instância NSManagedObject ser chamada na thread errada. Mas isto ainda pode acontecer. Então certifique-se quando estiver em desenvolvimento para ativar a asserção de depuração da thread -com.apple.CoreData.ConcurrencyDebug 1 Este artigo lhe dirá exatamente como fazê-lo e lhe informará sobre outras flags que você pode configurar para mais depuração. Use consultas agregadas CoreData oferece groupBy, sum, e count como funções agregadas que podem ser executadas durante uma consulta. A configuração pode ser um pouco complexa, mas vale bem a pena. A alternativa é criar uma consulta para obter todos os objetos que correspondem ao seu critério e depois executar um para loop através de todas as entidades e fazer as contas você mesmo. Mais fácil de codificar, mas muito menos eficiente. Quando você executa uma requisição agregada de fetch, o CoreData o traduz para uma consulta de banco de dados e executa a matemática diretamente no nível do banco de dados. Torna-o super rápido. Este artigo é uma boa introdução a como fazê-lo. Keep core data related code in categories Após ter a sua subclasse NSManagedObject criada pelo Xcode, muito provavelmente descobrirá que tem código que deve estar na classe que representa o seu objecto (para calcular valores, importar dados do seu servidor, etc). Mas você pediu ao CoreData para criar as classes para você… Então você terá que criar uma categoria para o objeto que precisa de comportamento extra. Somente coloque lá o código relacionado ao CoreData para que você possa mantê-lo limpo. Adote um modelo de transação para suas mudanças nos seus dados Você pode modificar seus objetos o quanto quiser, mas para persistir nessas mudanças você precisa chamar NSManagedObjectContext.save(). Você se verá rapidamente esquecendo de chamar save() e você terá algumas mudanças pendentes no seu contexto. Neste ponto todas as apostas estão canceladas, na próxima vez que você chamar save(), você não terá certeza do que está comprometendo. Adote um modelo de transação para evitar deixar um contexto sujo para trás. Para fazer isso, crie uma categoria em NSPersistentContainer assim: extension NSPersistentContainer {@objc func performBackgroundSaveTask(_ block: @escaping (NSManagedObjectContext) -> Void) {performBackgroundTask { (context: NSManagedObjectContext) inblock(context)context.save()}}} A partir de agora certifique-se de que quaisquer alterações que fizer são executadas num bloco agendado com performBackgroundSaveTask(). Desta forma você tem a garantia de que as alterações são salvas quando você as faz. Não salve o contexto de dentro de uma categoria de modelo método Nunca chame save() de dentro de uma categoria de entidade. Não há nada pior do que chamar um método no seu objeto sem perceber que ele vai salvar todo o contexto com ele. Cometendo algumas mudanças que você pode não querer salvar. A chamada de salvar deve ser feita no ponto onde as modificações no objeto são iniciadas. Articles