Comment gérer les instances temporaires de NSManagedObject?

86

J'ai besoin de créer des NSManagedObjectinstances, de faire des choses avec elles, puis de les détruire ou de les stocker dans sqlite db. Le problème est que je ne peux pas créer d'instances de NSManagedObjectnon connecté à NSManagedObjectContextet cela signifie que je dois clarifier d'une manière ou d'une autre après avoir décidé que je n'ai pas besoin de certains des objets de ma base de données.

Pour y faire face, j'ai créé un magasin en mémoire en utilisant le même coordinateur et j'y place des objets temporaires en utilisant assignObject:toPersistentStore.Maintenant, comment puis-je m'assurer que ces objets temporaires n'atteignent pas les données, que je récupère dans le commun au contexte des deux magasins? Ou dois-je créer des contextes séparés pour une telle tâche?


UPD:

Maintenant, je pense à créer un contexte séparé pour le stockage en mémoire. Comment déplacer des objets d'un contexte à un autre? Juste en utilisant [context insertObject:]? Cela fonctionnera-t-il correctement dans cette configuration? Si j'insère un objet du graphe d'objets, le graphe entier est-il également inséré dans le contexte?

fspirit
la source
Il doit s'agir d'une question distincte puisque vous avez marqué celle-ci comme une réponse. Créez une nouvelle question et expliquez POURQUOI vous pensez avoir besoin d'une pile de données principales distincte JUSTE pour un stockage en mémoire. Je serai heureux d'explorer la question avec vous.
Marcus S.Zarra
La section UPD n'est plus pertinente, car j'ai choisi une autre approche, voir mon dernier commentaire à votre réponse.
fspirit

Réponses:

146

REMARQUE: cette réponse est très ancienne. Voir les commentaires pour l'historique complet. Ma recommandation a depuis changé et je ne recommande plus d'utiliser des NSManagedObjectinstances non associées . Ma recommandation actuelle est d'utiliser des NSManagedObjectContextinstances enfants temporaires .

Réponse originale

Le moyen le plus simple de le faire est de créer vos NSManagedObjectinstances sans associé NSManagedObjectContext.

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

Ensuite, lorsque vous souhaitez le sauvegarder:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}
Marcus S. Zarra
la source
6
Si unassociatedObject a des références à d'autres objets non associés, dois-je les insérer un par un ou myMOC est suffisamment intelligent pour collecter toutes les références et les insérer également?
fspirit
6
Il est également assez intelligent pour gérer les relations.
Marcus S.Zarra
2
J'aime le fait que cette approche vous permette de traiter les MO comme des objets de données normaux avant de décider de les stocker, mais je m'inquiète de la façon dont le contrat CoreData est «pris en charge» et donc de sa pérennité. Apple mentionne-t-il ou utilise-t-il cette approche n'importe où? Parce que sinon, une future version iOS pourrait modifier les propriétés dynamiques pour dépendre du MOC et rompre cette approche. Les documents Apple ne sont pas clairs à ce sujet: ils soulignent l'importance du contexte et de l'initialiseur désigné, mais il y a une mention dans le document MO disant "si le contexte n'est pas nul, alors ..." suggérant que nul pourrait être correct
Rhubarb
41
J'ai utilisé cette approche il y a quelque temps, mais j'ai commencé à voir un comportement étrange et des plantages lorsque j'ai modifié ces objets et / ou créé des relations pour eux avant de les insérer dans un MOC. J'en ai discuté avec un ingénieur Core Data de la WWDC et il a dit que bien que l'API pour les objets non associés soit là, il a fortement recommandé de ne pas l'utiliser comme un MOC repose fortement sur les notifications KVO envoyées par ses objets. Il a suggéré d'utiliser NSObject standard pour les objets temporaires car c'est beaucoup plus sûr.
Adrian Schönig
7
Cela ne semble pas bien fonctionner avec iOS 8, en particulier avec des relations persistantes. Quelqu'un d'autre peut-il le confirmer?
Janum Trivedi
40

iOS5 offre une alternative plus simple à la réponse de Mike Weller. À la place, utilisez un NSManagedObjectContext enfant . Il supprime le besoin de trampoline via NSNotificationCenter

Pour créer un contexte enfant:

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

Créez ensuite vos objets en utilisant le contexte enfant:

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

Les modifications ne sont appliquées que lorsque le contexte enfant est enregistré. Donc, pour annuler les modifications, ne sauvegardez pas.

Il y a toujours une limitation sur les relations. c'est-à-dire que vous ne pouvez pas créer de relations avec des objets dans d'autres contextes. Pour contourner cela, utilisez objectID, pour obtenir l'objet du contexte enfant. par exemple.

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

Notez que l'enregistrement du contexte enfant applique les modifications au contexte parent. L'enregistrement du contexte parent persiste les modifications.

Voir wwdc 2012 session 214 pour une explication complète.

chemin de fer
la source
1
Merci de l'avoir suggéré! J'ai écrit une démo pour tester cette méthode par rapport à l'utilisation d'un contexte nul et au moins sur OSX, cela a fonctionné lors de l'insertion d'un contexte nul perdu ses attributs lors de l'enregistrement - démo sur github.com/seltzered/CoreDataMagicalRecordTempObjectsDemo
Vivek Gani
Lequel se trouve mocdans le troisième extrait? Est-ce childContextou myMangedObjectContext?
bugloaf
It is the
childContext
cette solution est meilleure que d'avoir le contexte nul.
Will Y
Puisque NSManagedObjectfournit déjà le pertinent NSManagedObjectContext, vous pouvez automatiser le choix du contexte: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];et puis objectWithRelationship.relationship = objectRelatedContextually;.
Gary
9

La bonne façon de réaliser ce genre de chose est d'utiliser un nouveau contexte d'objet géré. Vous créez un contexte d'objet géré avec le même magasin persistant:

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

Ensuite, vous ajoutez de nouveaux objets, vous les mutez, etc.

Quand vient le temps de sauvegarder, vous devez appeler [tempContext save: ...] sur tempContext et gérer la notification de sauvegarde pour la fusionner dans votre contexte d'origine. Pour supprimer les objets, libérez simplement ce contexte temporaire et oubliez-le.

Ainsi, lorsque vous enregistrez le contexte temporaire, les modifications sont conservées dans le magasin, et il vous suffit de récupérer ces modifications dans votre contexte 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];

C'est également ainsi que vous devez gérer les opérations de données de base multi-thread. Un contexte par thread.

Si vous avez besoin d'accéder à des objets existants à partir de ce contexte temporaire (pour ajouter des relations, etc.), vous devez utiliser l'ID de l'objet pour obtenir une nouvelle instance comme celle-ci:

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

Si vous essayez d'utiliser un NSManagedObjectdans le mauvais contexte, vous obtiendrez des exceptions lors de l'enregistrement.

Mike Weller
la source
Créer un deuxième contexte juste pour cela est très NSManagedObjectContextcoûteux car le fait de tenir debout coûte cher en mémoire et en CPU. Je me rends compte que cela figurait à l'origine dans certains des exemples Apple, mais ils ont mis à jour et corrigé ces exemples.
Marcus S.Zarra
2
Apple utilise toujours cette technique (création d'un deuxième contexte d'objet géré) pour l'exemple de code CoreDataBooks.
nevan king
1
Remarque Apple a mis à jour CoreDataBooks, en effet il utilise toujours deux contextes, mais maintenant le 2ème contexte est un enfant du premier. Cette technique est discutée (et recommandée) dans la présentation 303 de la WWDC 2011 (ce qui est nouveau dans Core Data dans iOS) et est mentionnée ici (avec le code beaucoup, BEAUCOUP, plus simple pour fusionner les modifications vers le haut) stackoverflow.com/questions/9791469/…
Rhubarb
4
"Créer un deuxième contexte juste pour cela est très coûteux car la mise en place d'un NSManagedObjectContext coûte cher en mémoire et en CPU." . Non ce n'est pas. Les dépendances du coordinateur de magasin persistant (modèle d'objet géré et magasins concrets) ne sont pas le contexte. Les contextes sont légers.
quellish
3
@quellish D'accord. Apple a déclaré dans ses récentes discussions sur les performances des données de base à la WWDC que la création de contextes était très légère.
Jesse
9

La création d'objets temporaires à partir d'un contexte nul fonctionne bien jusqu'à ce que vous essayiez réellement d'avoir une relation avec un objet dont le contexte! = Nil!

assurez-vous que vous êtes d'accord avec cela.

user134611
la source
Je ne suis pas d'accord avec ça
Charlie
8

Ce que vous décrivez est exactement à quoi NSManagedObjectContextsert un .

Tiré du Guide de programmation des données de base: Principes de base des données de base

Vous pouvez considérer un contexte d'objet géré comme un bloc-notes intelligent. Lorsque vous récupérez des objets à partir d'un magasin persistant, vous apportez des copies temporaires sur le bloc-notes où elles forment un graphique d'objets (ou une collection de graphiques d'objets). Vous pouvez ensuite modifier ces objets comme vous le souhaitez. Sauf si vous enregistrez réellement ces modifications, cependant, le magasin persistant reste inchangé.

Et Guide de programmation des données de base: validation d'objets gérés

Cela sous-tend également l'idée d'un contexte d'objet géré représentant un «bloc-notes». En général, vous pouvez amener des objets gérés sur le bloc-notes et les modifier comme vous le souhaitez avant de valider les modifications ou de les annuler.

NSManagedObjectContexts sont conçus pour être légers. Vous pouvez les créer et les supprimer à volonté - c'est le coordinateur des magasins persistants et ce sont les dépendances qui sont "lourdes". Un seul coordinateur de magasin persistant peut être associé à de nombreux contextes. Dans l'ancien modèle de confinement des threads obsolète, cela signifierait définir le même coordinateur de magasin persistant sur chaque contexte. Aujourd'hui, cela signifierait connecter des contextes imbriqués à un contexte racine associé au coordinateur de magasin persistant.

Créez un contexte, créez et modifiez des objets gérés dans ce contexte. Si vous souhaitez les conserver et communiquer ces modifications, enregistrez le contexte. Sinon, jetez-le.

Tenter de créer des objets gérés indépendamment d'un NSManagedObjectContextpose problème. N'oubliez pas que Core Data est en fin de compte un mécanisme de suivi des modifications pour un graphique d'objets. Pour cette raison, les objets gérés font vraiment partie du contexte des objets gérés . Le contexte observe leur cycle de vie et sans le contexte, toutes les fonctionnalités des objets gérés ne fonctionneront pas correctement.

apaiser
la source
6

En fonction de votre utilisation de l'objet temporaire, il y a quelques mises en garde aux recommandations ci-dessus. Mon cas d'utilisation est que je souhaite créer un objet temporaire et le lier à des vues. Lorsque l'utilisateur choisit d'enregistrer cet objet, je souhaite configurer les relations avec les objets existants et les enregistrer. Je veux faire cela pour éviter de créer un objet temporaire pour contenir ces valeurs. (Oui, je pourrais simplement attendre que l'utilisateur enregistre, puis récupère le contenu de la vue, mais je mets ces vues à l'intérieur d'une table et la logique pour le faire est moins élégante.)

Les options pour les objets temporaires sont:

1) (Préféré) Créez l'objet temporaire dans un contexte enfant. Cela ne fonctionnera pas car je lie l'objet à l'interface utilisateur et je ne peux pas garantir que les accesseurs d'objet sont appelés sur le contexte enfant. (Je n'ai trouvé aucune documentation indiquant le contraire, je dois donc supposer.)

2) Créez l'objet temporaire avec un contexte d'objet nul. Cela ne fonctionne pas et entraîne une perte / corruption de données.

Ma solution: J'ai résolu ce problème en créant l'objet temporaire avec un contexte d'objet nul, mais lorsque j'enregistre l'objet, plutôt que de l'insérer en tant que n ° 2, je copie tous ses attributs dans un nouvel objet que je crée dans le contexte principal. J'ai créé une méthode de prise en charge dans ma sous-classe NSManagedObject appelée cloneInto: qui me permet de copier facilement les attributs et les relations pour n'importe quel objet.

Greg
la source
C'est ce que je recherche. Mais mon doute est de savoir comment allez-vous gérer les attributs de la relation?
Mani
1

Pour moi, la réponse de Marcus n'a pas fonctionné. Voici ce qui a fonctionné pour moi:

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

puis, si je décide de le sauvegarder:

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

Il ne faut pas non plus oublier de le publier

[unassociatedObject release]
Lucas
la source
1

Je réécris cette réponse pour Swift comme toutes les questions similaires pour une redirection rapide vers cette question.

Vous pouvez déclarer l'objet sans ManagedContext à l'aide du code suivant.

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

Plus tard, pour enregistrer l'objet, vous pouvez l'insérer dans le contexte et l'enregistrer.

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