Todo Core Data Relacionamento tem que ter um Inverso?

Digamos que eu tenha duas classs de entidade: SocialApp e SocialAppType

No SocialApp eu tenho um atributo: appURL e um relacionamento: type .

Em SocialAppType eu tenho três atributos: baseURL , name e favicon .

O destino do type relacionamento do SocialApp é um único registro no SocialAppType .

Como exemplo, para várias contas do Flickr, haveria vários registros do SocialApp , com cada registro contendo um link para a conta de uma pessoa. Haveria um registro SocialAppType para o tipo “Flickr”, para o qual todos os registros do SocialApp apontariam.

Quando eu construo uma aplicação com este esquema, recebo um aviso de que não existe relação inversa entre SocialAppType e SocialApp .

  /Users/username/Developer/objc/TestApp/TestApp.xcdatamodel:SocialApp.type: warning: SocialApp.type -- relationship does not have an inverse 

Eu preciso de um inverso e por quê?

Na prática, eu não tive nenhuma perda de dados devido a não ter um inverso – pelo menos que eu saiba. Um rápido Google sugere que você deve usá-los:

Um relacionamento inverso não apenas torna as coisas mais organizadas, como também é usado pelo Core Data para manter a integridade dos dados.

– Cocoa Dev Central

Você deve modelar os relacionamentos em ambas as direções e especificar os relacionamentos inversos adequadamente. O Core Data usa essas informações para garantir a consistência do gráfico do object, se uma alteração for feita (consulte “Manipulando Relacionamentos e Integridade do Gráfico do Objeto”). Para uma discussão de algumas das razões pelas quais você pode querer não modelar um relacionamento em ambas as direções, e alguns dos problemas que podem surgir se você não o fizer, consulte “Relacionamentos unidirecionais”.

– Core Data Programming Guide

A documentação da Apple tem um ótimo exemplo que sugere uma situação em que você pode ter problemas por não ter um relacionamento inverso. Vamos mapeá-lo neste caso.

Suponha que você modelou da seguinte forma: insira a descrição da imagem aqui

Observe que você tem um relacionamento to-one chamado ” type “, do SocialApp ao SocialAppType . O relacionamento não é opcional e tem uma regra de exclusão “negar” .

Agora considere o seguinte:

 SocialApp *socialApp; SocialAppType *appType; // assume entity instances correctly instantiated [socialApp setSocialAppType:appType]; [managedObjectContext deleteObject:appType]; BOOL saved = [managedObjectContext save:&error]; 

O que esperamos é falhar nesta salvaguarda de contexto, uma vez que definimos a regra de exclusão como Negar enquanto a relação não é opcional.

Mas aqui o save tem sucesso.

A razão é que não estabelecemos um relacionamento inverso . Por isso, a instância do socialApp não é marcada como alterada quando o appType é excluído. Portanto, nenhuma validação acontece para o socialApp antes de salvar (ele não assume nenhuma validação necessária desde que nenhuma mudança aconteceu). Mas, na verdade, uma mudança aconteceu. Mas isso não se reflete.

Se lembrarmos appType por

 SocialAppType *appType = [socialApp socialAppType]; 

appType é nulo.

Estranho, não é? Ficamos nulos por um atributo não opcional?

Então você não está em apuros se tiver estabelecido o relacionamento inverso. Caso contrário, você terá que forçar a validação escrevendo o código da seguinte maneira.

 SocialApp *socialApp; SocialAppType *appType; // assume entity instances correctly instantiated [socialApp setSocialAppType:appType]; [managedObjectContext deleteObject:appType]; [socialApp setValue:nil forKey:@"socialAppType"] BOOL saved = [managedObjectContext save:&error]; 

Vou parafrasear a resposta definitiva que encontrei em Mais Desenvolvimento do iPhone 3, por Dave Mark e Jeff LeMarche.

A Apple geralmente recomenda que você sempre crie e especifique o inverso, mesmo que você não use o relacionamento inverso em seu aplicativo. Por esse motivo, ele avisa quando você não fornece um inverso.

Os relacionamentos não precisam ter um inverso, porque existem alguns cenários em que o relacionamento inverso pode prejudicar o desempenho. Por exemplo, suponha que o relacionamento inverso contenha um número extremamente grande de objects. Remover o inverso requer iteração sobre o conjunto que representa o desempenho inverso e enfraquecedor.

Mas a menos que você tenha uma razão específica para não modelar o inverso . Ele ajuda o Core Data a garantir a integridade dos dados. Se você tiver problemas de desempenho, é relativamente fácil remover o relacionamento inverso posteriormente.

A melhor pergunta é: “existe uma razão para não ter um inverso”? O Core Data é, na verdade, um framework de gerenciamento de grafos de objects, não um framework de persistência. Em outras palavras, seu trabalho é gerenciar os relacionamentos entre objects no gráfico de objects. Relacionamentos inversos tornam isso muito mais fácil. Por esse motivo, o Core Data espera relacionamentos inversos e é escrito para esse caso de uso. Sem eles, você terá que gerenciar a consistência do gráfico de objects por conta própria. Em particular, muitos relacionamentos sem um relacionamento inverso provavelmente serão corrompidos pelo Core Data, a menos que você trabalhe arduamente para manter as coisas funcionando. O custo em termos de tamanho de disco para as relações inversas é realmente insignificante em comparação com o benefício que ele recebe.

Há pelo menos um cenário em que um bom caso pode ser feito para um relacionamento de core data sem um inverso: quando já existe outro relacionamento de core data entre os dois objects, que manipulará a manutenção do gráfico de object.

Por exemplo, um livro contém muitas páginas, enquanto uma página está em um livro. Esse é um relacionamento de dois para um. A exclusão de uma página apenas anula o relacionamento, enquanto a exclusão de um livro também excluirá a página.

No entanto, você também pode querer acompanhar a página atual sendo lida para cada livro. Isso pode ser feito com uma propriedade “currentPage” na página , mas você precisa de outra lógica para garantir que apenas uma página do livro seja marcada como a página atual a qualquer momento. Em vez disso, criar um relacionamento currentPage do Book em uma única página garantirá que sempre haja apenas uma página atual marcada e, além disso, que essa página possa ser acessada facilmente com uma referência ao livro com apenas book.currentPage.

Apresentação gráfica da relação de dados básicos entre livros e páginas

Qual seria o relacionamento recíproco nesse caso? Algo em grande parte sem sentido. “myBook” ou semelhante pode ser adicionado de volta na outra direção, mas contém apenas as informações já contidas no relacionamento “livro” da página e, portanto, cria seus próprios riscos. Talvez no futuro, a maneira como você está usando um desses relacionamentos seja alterada, resultando em alterações na configuração de core data. Se page.myBook foi usado em alguns lugares onde page.book deveria ter sido usado no código, pode haver problemas. Outra maneira de evitar isso de forma proativa seria também não expor myBook na subclass NSManagedObject que é usada para acessar a página. No entanto, pode-se argumentar que é mais simples não modelar o inverso em primeiro lugar.

No exemplo descrito, a regra de exclusão para o relacionamento currentPage deve ser definida como “Sem ação” ou “Cascata”, pois não há relação recíproca com “Nullify”. (Cascade implica que você está rasgando todas as páginas do livro enquanto lê, mas isso pode ser verdade se você estiver particularmente frio e precisar de combustível.)

Quando é possível demonstrar que a integridade do gráfico de object não está em risco, como neste exemplo, e a complexidade e a capacidade de manutenção do código são aprimoradas, pode-se argumentar que uma relação sem um inverso pode ser a decisão correta.

Embora os documentos não pareçam exigir um inverso, acabei de resolver um cenário que de fato resultou em “perda de dados” por não ter um inverso. Eu tenho um object de relatório que tem um relacionamento muitos em objects reportáveis. Sem o relacionamento inverso, quaisquer mudanças no relacionamento entre muitos foram perdidas no relançamento. Depois de inspecionar a debugging do Core Data, ficou aparente que, embora eu estivesse salvando o object de relatório, as atualizações para o gráfico de objects (relacionamentos) nunca estavam sendo feitas. Eu adicionei um inverso, apesar de não usá-lo, e voila, funciona. Portanto, pode não ser necessário, mas relacionamentos sem inversão podem ter efeitos colaterais estranhos.