Como lidar com instâncias temporárias de NSManagedObject?

86

Preciso criar NSManagedObjectinstâncias, fazer algumas coisas com elas e, em seguida, jogá-las no lixo ou armazená-las em sqlite db. O problema é que não consigo criar instâncias de NSManagedObjectnão conectado a NSManagedObjectContexte isso significa que tenho que limpar de alguma forma depois de decidir que não preciso de alguns dos objetos em meu banco de dados.

Para lidar com isso, eu criei um armazenamento na memória usando o mesmo coordenador e estou colocando objetos temporários lá usando assignObject:toPersistentStore.Agora, como posso garantir que esses objetos temporários não cheguem aos dados, que eu busco do comum ao contexto de ambas as lojas? Ou devo criar contextos separados para tal tarefa?


UPD:

Agora estou pensando em criar um contexto separado para armazenamento na memória. Como faço para mover objetos de um contexto para outro? Apenas usando [context insertObject:]? Funcionará bem nesta configuração? Se eu inserir um objeto do gráfico de objetos, o gráfico inteiro também é inserido no contexto?

espírito
fonte
Esta deve ser uma pergunta separada, já que você sinalizou esta como respondida. Crie uma nova pergunta e explique POR QUE você acha que precisa de uma pilha inteira de Core Data APENAS para um armazenamento na memória. Terei prazer em explorar a questão com você.
Marcus S. Zarra
A seção UPD agora não é relevante, porque eu escolhi outra abordagem, veja meu último comentário à sua resposta.
fspirit

Respostas:

146

NOTA: Esta resposta é muito antiga. Veja os comentários para a história completa. Minha recomendação mudou desde então e eu não recomendo mais o uso de NSManagedObjectinstâncias não associadas . Minha recomendação atual é usar NSManagedObjectContextinstâncias filho temporárias .

Resposta Original

A maneira mais fácil de fazer isso é criar suas NSManagedObjectinstâncias sem um associado NSManagedObjectContext.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

Então, quando você quiser salvá-lo:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}
Marcus S. Zarra
fonte
6
Se unassociatedObject tiver refs para outros objetos não associados, devo inseri-los um por um ou myMOC é inteligente o suficiente para coletar todos os refs e inseri-los também?
fspirit
6
É inteligente o suficiente para lidar com os relacionamentos também.
Marcus S. Zarra
2
Gosto do fato de que essa abordagem permite que você trate os MOs como objetos de dados regulares antes de decidir armazená-los, mas estou preocupado com o quão "suportado" pelo contrato CoreData e, portanto, quão à prova de futuro ele é. A apple menciona ou usa essa abordagem em algum lugar? Porque do contrário, uma versão futura do iOS poderia alterar as propriedades dinâmicas para depender do MOC e quebrar essa abordagem. Os documentos da apple não são claros sobre isso: eles enfatizam a importância do contexto e do inicializador designado, mas há uma menção no documento de MO dizendo "se o contexto não for nulo, então ..." sugerindo que nulo pode ser ok
Ruibarbo
41
Usei essa abordagem há algum tempo, mas comecei a ver comportamentos estranhos e travamentos quando modifiquei esses objetos e / ou criei relacionamentos para eles antes de inseri-los em um MOC. Conversei sobre isso com um engenheiro de dados centrais da WWDC e ele disse que, embora a API para objetos não associados esteja lá, ele recomendou fortemente não usá-la como um MOC que depende fortemente de notificações KVO enviadas por seus objetos. Ele sugeriu usar NSObject regular para objetos temporários, pois é muito mais seguro.
Adrian Schönig
7
Isso não parece funcionar bem com o iOS 8, especialmente com relacionamentos persistentes. Mais alguém pode confirmar isso?
Janum Trivedi de
39

iOS5 oferece uma alternativa mais simples para a resposta de Mike Weller. Em vez disso, use uma criança NSManagedObjectContext . Ele elimina a necessidade de trampolim por meio do NSNotificationCenter

Para criar um contexto filho:

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = myMangedObjectContext;

Em seguida, crie seus objetos usando o contexto filho:

NSManagedObject *o = [NSEntityDescription insertNewObjectForEntityForName:@"MyObject" inManagedObjectContext:childContext];

As alterações são aplicadas apenas quando o contexto filho é salvo. Portanto, para descartar as alterações, apenas não salve.

Ainda existe uma limitação nos relacionamentos. ou seja, você não pode criar relacionamentos com objetos em outros contextos. Para contornar isso, use objectID's para obter o objeto do contexto filho. por exemplo.

NSManagedObjectID *mid = [myManagedObject objectID];
MyManagedObject *mySafeManagedObject = [childContext objectWithID:mid];
object.relationship=mySafeManagedObject;

Observe que salvar o contexto filho aplica as alterações ao contexto pai. Salvar o contexto pai persiste as alterações.

Consulte a sessão 214 do wwdc 2012 para obter uma explicação completa.

parada ferroviária
fonte
1
Obrigado por sugerir isso! Escrevi uma demonstração testando esse método em comparação com o uso de um contexto nulo e pelo menos no OSX, isso funcionou enquanto a inserção de um contexto nulo perdia seus atributos ao salvar - demonstração em github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo
Vivek Gani
Qual está mocno terceiro trecho? É childContextou myMangedObjectContext?
bugloaf de
É o childContext
railwayparade
esta solução é melhor do que ter o contexto nulo.
Será em
Uma vez que NSManagedObjectjá fornece o relevante NSManagedObjectContext, você pode automatizar a escolha do contexto: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];e então objectWithRelationship.relationship = objectRelatedContextually;.
Gary
9

A maneira correta de conseguir esse tipo de coisa é com um novo contexto de objeto gerenciado. Você cria um contexto de objeto gerenciado com o mesmo armazenamento persistente:

NSManagedObjectContext *tempContext = [[[NSManagedObjectContext alloc] init] autorelease];
[tempContext setPersistentStore:[originalContext persistentStore]];

Em seguida, você adiciona novos objetos, os transforma, etc.

Quando chega a hora de salvar, você precisa chamar [tempContext save: ...] no tempContext e manipular a notificação de salvamento para mesclar isso ao seu contexto original. Para descartar os objetos, basta liberar este contexto temporário e esquecê-lo.

Então, quando você salva o contexto temporário, as alterações são mantidas na loja, e você só precisa colocar essas alterações de volta em seu contexto principal:

/* Called when the temp context is saved */
- (void)tempContextSaved:(NSNotification *)notification {
    /* Merge the changes into the original managed object context */
    [originalContext mergeChangesFromContextDidSaveNotification:notification];
}

// Here's where we do the save itself

// Add the notification handler
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(tempContextSaved:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:tempContext];

// Save
[tempContext save:NULL];
// Remove the handler again
[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:NSManagedObjectContextDidSaveNotification
                                              object:tempContext];

Essa também é a maneira pela qual você deve lidar com operações de dados centrais multithread. Um contexto por thread.

Se você precisa acessar objetos existentes a partir deste contexto temporário (para adicionar relações, etc.), você precisa usar o ID do objeto para obter uma nova instância como esta:

NSManagedObject *objectInOriginalContext = ...;
NSManagedObject *objectInTemporaryContext = [tempContext objectWithID:[objectInOriginalContext objectID]];

Se você tentar usar um NSManagedObjectno contexto errado, obterá exceções ao salvar.

Mike Weller
fonte
Criar um segundo contexto apenas para isso é um desperdício, pois levantar um NSManagedObjectContexté caro tanto em memória quanto em CPU. Eu sei que isso estava originalmente em alguns dos exemplos da Apple, mas eles atualizaram e corrigiram esses exemplos.
Marcus S. Zarra
2
A Apple ainda está usando essa técnica (criando um segundo contexto de objeto gerenciado) para o código de exemplo CoreDataBooks.
nevan king,
1
Nota A Apple atualizou CoreDataBooks, na verdade ele ainda usa dois contextos, mas agora o segundo contexto é filho do primeiro. Esta técnica é discutida (e recomendada) na apresentação 303 do WWDC 2011 (o que há de novo em Core Data no iOS) e é mencionada aqui (com o código muito, MUITO, mais simples para mesclar alterações para cima) stackoverflow.com/questions/9791469/…
Ruibarbo
4
"Criar um segundo contexto apenas para isso é um desperdício, pois montar um NSManagedObjectContext é caro tanto em memória quanto em CPU." . Não, não é. As dependências do coordenador de armazenamento persistente (modelo de objeto gerenciado e armazenamento concreto) são, não o contexto. Os contextos são leves.
quellish de
3
@quellish Concordo. A Apple declarou em suas conversas recentes sobre desempenho de dados básicos na WWDC que criar contextos é muito leve.
Jesse
9

Criar objetos temporários de contexto nulo funciona bem até que você realmente tente ter um relacionamento com um objeto cujo contexto! = Nulo!

certifique-se de que você concorda com isso.

user134611
fonte
Não estou bem com isso
Charlie
8

O que você está descrevendo é exatamente para que NSManagedObjectContextserve.

Do Core Data Programming Guide: Core Data Basics

Você pode pensar em um contexto de objeto gerenciado como um bloco de notas inteligente. Ao buscar objetos de um armazenamento persistente, você traz cópias temporárias para o bloco de notas, onde formam um gráfico de objeto (ou uma coleção de gráficos de objeto). Você pode então modificar esses objetos como quiser. A menos que você realmente salve essas alterações, o armazenamento persistente permanece inalterado.

E Guia de Programação de Dados Principais: Validação de Objeto Gerenciado

Isso também sustenta a ideia de um contexto de objeto gerenciado que representa um "bloco de notas" - em geral, você pode trazer objetos gerenciados para o bloco de notas e editá-los como desejar antes de, em última análise, confirmar as alterações ou descartá-los.

NSManagedObjectContexts são projetados para serem leves. Você pode criá-los e descartá-los à vontade - é o coordenador de armazenamento persistente e suas dependências que são "pesadas". Um único coordenador de loja persistente pode ter muitos contextos associados a ele. No modelo de confinamento de thread mais antigo e obsoleto, isso significaria definir o mesmo coordenador de armazenamento persistente em cada contexto. Hoje, isso significaria conectar contextos aninhados a um contexto raiz que está associado ao coordenador de armazenamento persistente.

Crie um contexto, crie e modifique objetos gerenciados dentro desse contexto. Se você deseja mantê-los e comunicar essas mudanças, salve o contexto. Caso contrário, descarte-o.

Tentar criar objetos gerenciados independentes de um NSManagedObjectContexté procurar problemas. Lembre-se de que o Core Data é basicamente um mecanismo de controle de alterações para um gráfico de objeto. Por causa disso, os objetos gerenciados são realmente parte do contexto do objeto gerenciado . O contexto observa seu ciclo de vida e, sem o contexto, nem todas as funcionalidades do objeto gerenciado funcionarão corretamente.

abafado
fonte
6

Dependendo do uso do objeto temporário, há algumas ressalvas às recomendações acima. Meu caso de uso é que desejo criar um objeto temporário e vinculá-lo a visualizações. Quando o usuário opta por salvar este objeto, desejo configurar relacionamentos com o (s) objeto (s) existente (s) e salvá-lo. Quero fazer isso para evitar a criação de um objeto temporário para conter esses valores. (Sim, eu poderia apenas esperar até que o usuário salve e, em seguida, pegue o conteúdo da visualização, mas estou colocando essas visualizações dentro de uma tabela e a lógica para fazer isso é menos elegante.)

As opções para objetos temporários são:

1) (Preferencial) Crie o objeto temporário em um contexto filho. Isso não funcionará porque estou vinculando o objeto à IU e não posso garantir que os acessadores do objeto sejam chamados no contexto filho. (Não encontrei nenhuma documentação que indique o contrário, portanto, devo assumir.)

2) Crie o objeto temporário com contexto de objeto nulo. Isso não funciona e resulta em perda / corrupção de dados.

Minha solução: Resolvi isso criando o objeto temporário com contexto de objeto nulo, mas quando salvo o objeto, em vez de inseri-lo como # 2, copio todos os seus atributos em um novo objeto que criei no contexto principal. Eu criei um método de suporte em minha subclasse NSManagedObject chamado cloneInto: que me permite copiar atributos e relacionamentos facilmente para qualquer objeto.

Greg
fonte
É isso que estou procurando. Mas minha dúvida é como você vai lidar com os atributos de relacionamento?
Mani,
1

Para mim, a resposta de Marcus não funcionou. Aqui está o que funcionou para mim:

NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

então, se eu decidir salvá-lo:

[myMOC insertObject:unassociatedObjet];
NSError *error = nil;
[myMoc save:&error];
//Check the error!

Também não devemos esquecer de liberá-lo

[unassociatedObject release]
Lucas
fonte
1

Estou reescrevendo esta resposta para o Swift, pois todas as perguntas semelhantes para o Swift redirecionam para esta pergunta.

Você pode declarar o objeto sem qualquer ManagedContext usando o código a seguir.

let entity = NSEntityDescription.entity(forEntityName: "EntityName", in: myContext)
let unassociatedObject = NSManagedObject.init(entity: entity!, insertInto: nil)

Posteriormente, para salvar o objeto, você pode inseri-lo no contexto e salvá-lo.

myContext.insert(unassociatedObject)
// Saving the object
do {
    try self.stack.saveContext()
    } catch {
        print("save unsuccessful")
    }
}
Mitul Jindal
fonte