Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Permanent NSManagedObjectID not so permanent?

I'm having trouble dealing with object IDs in CoreData. I'm using MagicalRecord for convenience and have 3 contexts: a private queue working context, a main queue context for UI and parent to the working context, and a private queue saving context that is the parent of the main context.

My goal is to create an object in the working context, save to the persistent store, save it's objectID URL to NSUserDefaults, and then be able to pull out that MO using the objectID later on. However, what I'm finding is that after saving the permanent ID of the object is changing.

In the console output below you can see that after I request the permanent ID the value I get back is "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1" but later when I list all the objects in CD the only object there has the ID "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2". p1 vs p2, what happened?

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);

      });
  }];

Console output:

  > +[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>)"
  )}
like image 903
brianpartridge Avatar asked Aug 31 '12 15:08

brianpartridge


2 Answers

Short answer is, don't do that :)

-objectID is not reliable between launches of your application. It is guaranteed to be unique and reliable under the following conditions:

  1. Within a single lifecycle of the application
  2. In its original object form (not in URL or NSString form)

Treating the -objectID as a permanent unique identifier to be stored outside of the persistent store is going to fail you fairly often. Core Data changes the underlying details of the -objectID many times during the life of the object.

If you need an externally reliable unique then you need to create one yourself. I generally recommend adding a [[NSProcessInfo processInfo] globallyUniqueString] to any entity that needs an externally reference-able unique. -awakeFromInsert is a great place to do that.

like image 104
Marcus S. Zarra Avatar answered Feb 01 '23 23:02

Marcus S. Zarra


This may be because you're using nested context.

#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;
}

Outputs:

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>
like image 45
Daniel Eggert Avatar answered Feb 02 '23 01:02

Daniel Eggert