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>)"
)}
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:
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With