2012-08-31 3 views
8

J'ai des problèmes avec les ID d'objet dans CoreData. J'utilise MagicalRecord pour plus de commodité et j'ai 3 contextes: un contexte de travail de file d'attente privée, un contexte de file d'attente principal pour l'interface utilisateur et parent pour le contexte de travail et un contexte d'enregistrement de file d'attente privé parent du contexte principal.Permanent NSManagedObjectID pas si permanent?

Mon but est de créer un objet dans le contexte de travail, de l'enregistrer dans le magasin persistant, de sauvegarder son URL objectID dans NSUserDefaults, puis de retirer ce MO en utilisant l'objet objectID plus tard. Cependant, ce que je trouve est qu'après avoir enregistré l'identification permanente de l'objet change.

Dans la sortie de console ci-dessous, vous pouvez voir qu'après avoir demandé l'ID permanent, la valeur que je récupère est "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1" mais plus tard le seul objet a l'ID "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2". p1 vs p2, que s'est-il passé?

code:

NSManagedObjectContext *c = [NSManagedObjectContext MR_contextThatPushesChangesToDefaultContext]; 
    [c performBlockAndWait:^{ 

     NSArray *all = [CDBaseAccount MR_findAllInContext:c]; 
     NSLog(@"count: %d", all.count); 
     NSLog(@"all accounts = %@", all); 

     CDBaseAccount *a = [CDBaseAccount MR_createInContext:c]; 
     a.accountName = @"foo"; 

     [c MR_saveNestedContexts]; 

     NSLog(@"temp a.objectID = %@", a.objectID); 

     NSError *error; 
     if (![c obtainPermanentIDsForObjects:@[a] error:&error]) { 
      NSLog(@"perm id error: %@", error); 
      return; 
     } 

     NSLog(@"perm a.objectID = %@", a.objectID); 

     NSURL *u = a.objectID.URIRepresentation; 

     dispatch_async(dispatch_get_main_queue(), ^{ 
      NSManagedObjectContext *d = [NSManagedObjectContext MR_defaultContext]; 

      NSArray *all = [CDBaseAccount MR_findAllInContext:d]; 
      NSLog(@"count: %d", all.count); 
      NSLog(@"all accounts = %@", all); 

      NSManagedObjectID *i = [d.persistentStoreCoordinator managedObjectIDForURIRepresentation:u]; 
      NSError *objWithIdError = nil; 
      NSManagedObject *o = [d existingObjectWithID:i error:&objWithIdError]; 
      if (objWithIdError != nil) { 
       NSLog(@"existing object error: %@", objWithIdError); 
       return; 
      } 

      NSLog(@"o = %@", o); 
      NSLog(@"o.objectID = %@", o.objectID); 

     }); 
    }]; 

Sortie de la console:

> +[NSManagedObjectContext(MagicalRecord) MR_contextWithStoreCoordinator:](0xa7c9b0) -> Created <NSManagedObjectContext: 0x83522a0>: Context *** MAIN THREAD *** 
    > count: 0 
    > all accounts = (
    >) 
    > -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x8353de0) -> Saving <NSManagedObjectContext: 0x8353de0>: Context *** MAIN THREAD *** 
    > -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x8195450) -> Saving <NSManagedObjectContext: 0x8195450>: *** DEFAULT *** Context *** MAIN THREAD *** 
    > -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x83522a0) -> Saving <NSManagedObjectContext: 0x83522a0>: *** BACKGROUND SAVE *** Context *** MAIN THREAD *** 
    > temp a.objectID = 0x8187ee0 <x-coredata:///CDBaseAccount/tF392AC6A-3539-4F39-AC53-35F9E5B3C9322> 
    > perm a.objectID = 0x8355800 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2> 
    > count: 1 
    > all accounts = (
     "<CDBaseAccount: 0x844ca60> (entity: CDBaseAccount; id: 0x844a4c0 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1> ; data: <fault>)" 
) 
    > existing object error: Error Domain=NSCocoaErrorDomain Code=133000 "The operation couldn’t be completed. (Cocoa error 133000.)" UserInfo=0x864d8c0 {NSAffectedObjectsErrorKey=(
     "<CDBaseAccount: 0x864b8c0> (entity: CDBaseAccount; id: 0x86405c0 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2> ; data: <fault>)" 
)} 

Répondre

18

Réponse courte est, ne pas le faire :)

-objectID n'est pas fiable entre les lancements de votre application. Il est garanti des conditions uniques et fiables sous les suivantes:

  1. Dans un seul cycle de vie de l'application
  2. Dans sa forme d'objet original (pas dans l'URL ou sous forme NSString)

Le traitement de la -objectID comme un identifiant unique permanent à stocker en dehors du magasin persistant va vous échouer assez souvent. Les données de base modifient les détails sous-jacents du -objectID plusieurs fois au cours de la vie de l'objet.

Si vous avez besoin d'un unique externe fiable, vous devez en créer un vous-même. Je recommande généralement d'ajouter un [[NSProcessInfo processInfo] globallyUniqueString] à toute entité qui a besoin d'une référence unique externe. -awakeFromInsert est un bon endroit pour le faire.

+0

Merci, tant Marcus! Le terme «permanent» dans toute la documentation est vraiment décevant. J'espère vraiment que vous avez sorti une nouvelle édition de votre livre de CD, puisque l'ancien est OOP. – brianpartridge

+1

+1 pour le 'globalallyUniqueString' –

+1

Vous faites certainement quelque chose de sommaire si un' NSManagedObjectID' qui retourne 'NON' à' isTemporaryID' change entre l'application lancée. Cela fonctionne très bien pour moi et l'a fait depuis longtemps. –

0

Cela peut être dû au fait que vous utilisez un contexte imbriqué.

#import <Foundation/Foundation.h> 
#import <CoreData/CoreData.h> 

static NSArray *fetchAllPersons(NSManagedObjectContext *moc); 
static NSManagedObjectModel *managedObjectModel(); 
static NSManagedObjectContext *createManagedObjectContext(); 
static NSURL *desktopDirectoryURL(void); 

static void testObjectID(void); 



int main(int argc, const char * argv[]) 
{ 
    @autoreleasepool { 
     dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
      testObjectID(); 
     }); 
    } 
    dispatch_main(); 
    return 0; 
} 




static void testObjectID(void) 
{ 
    NSManagedObjectContext *c = createManagedObjectContext(); 
    [c performBlock:^{ 

     NSArray *all = fetchAllPersons(c); 
     NSLog(@"count: %lu", all.count); 
     NSLog(@"all accounts = %@", all); 

     NSManagedObject *a = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:c]; 
     [a setValue:@"foo" forKey:@"name"]; 

     NSLog(@"temp a.objectID = %@", a.objectID); 
     NSError *error = nil; 
     NSCAssert([c obtainPermanentIDsForObjects:@[a] error:&error], @"perm id error: %@", error); 
     NSLog(@"perm a.objectID = %@", a.objectID); 

     NSCAssert([c save:&error], @"Save failed: %@", error); 

     NSURL *u = a.objectID.URIRepresentation; 

     dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
      NSManagedObjectContext *d = createManagedObjectContext(); 

      NSArray *all = fetchAllPersons(c); 
      NSLog(@"count: %lu", all.count); 
      NSLog(@"all accounts = %@", all); 

      NSManagedObjectID *i = [d.persistentStoreCoordinator managedObjectIDForURIRepresentation:u]; 
      NSError *objWithIdError = nil; 
      NSManagedObject *o = [d existingObjectWithID:i error:&objWithIdError]; 
      NSCAssert(o != nil, @"existing object error: %@", objWithIdError); 

      NSLog(@"o = %@", o); 
      NSLog(@"o.objectID = %@", o.objectID); 
     }); 
    }]; 
} 


static NSArray *fetchAllPersons(NSManagedObjectContext *moc) 
{ 
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"]; 
    NSError *error = nil; 
    NSArray *result = [moc executeFetchRequest:request error:&error]; 
    NSCAssert(result != nil, @"Fetch failed: %@", error); 
    return result; 
} 

static NSManagedObjectModel *managedObjectModel() 
{ 
    static NSManagedObjectModel *mom = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     NSEntityDescription *personEntity = [[NSEntityDescription alloc] init]; 
     [personEntity setName:@"Person"]; 

     NSAttributeDescription *nameAttribute = [[NSAttributeDescription alloc] init]; 

     [nameAttribute setName:@"name"]; 
     [nameAttribute setAttributeType:NSStringAttributeType]; 

     [personEntity setProperties:@[nameAttribute]]; 

     mom = [[NSManagedObjectModel alloc] init]; 
     [mom setEntities:@[personEntity]]; 
    }); 
    return mom; 
} 


static NSManagedObjectContext *createManagedObjectContext() 
{ 
    static NSPersistentStoreCoordinator *coordinator; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel()]; 

     NSString *STORE_TYPE = NSSQLiteStoreType; 
     NSString *STORE_FILENAME = @"foobar.db"; 

     NSError *error; 
     NSURL *url = [desktopDirectoryURL() URLByAppendingPathComponent:STORE_FILENAME]; 

     NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE 
                   configuration:nil URL:url options:nil 
                     error:&error]; 

     if (newStore == nil) { 
      NSLog(@"Store Configuration Failure\n%@", 
        ([error localizedDescription] != nil) ? 
        [error localizedDescription] : @"Unknown Error"); 
     } 
    }); 

    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [moc setPersistentStoreCoordinator:coordinator]; 

    return moc; 
} 


static NSURL *desktopDirectoryURL(void) 
{ 
    static NSURL *URL; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     NSFileManager *fileManager = [[NSFileManager alloc] init]; 
     NSError *error; 
     URL = [fileManager URLForDirectory:NSDesktopDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; 
     NSCAssert(URL != nil, @"Could not access Desktop directory: %@", [error localizedDescription]); 
    }); 
    return URL; 
} 

Sorties:

count: 0 
all accounts = (
) 
temp a.objectID = 0x10180e640 <x-coredata:///Person/tB1D48677-0152-4DA9-8573-7C7532863B4E2> 
perm a.objectID = 0x101901bb0 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> 
count: 1 
all accounts = (
    "<NSManagedObject: 0x10180e5b0> (entity: Person; id: 0x101901bb0 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> ; data: {\n name = foo;\n})" 
) 
o = <NSManagedObject: 0x100416530> (entity: Person; id: 0x100415b60 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> ; data: { 
    name = foo; 
}) 
o.objectID = 0x100415b60 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> 
+0

Ouais, c'est le comportement que je m'attendais. Peut-être est-ce lié à des contextes imbriqués. – brianpartridge