【问题标题】:Permanent NSManagedObjectID not so permanent?永久 NSManagedObjectID 不是那么永久吗?
【发布时间】:2012-08-26 10:50:27
【问题描述】:

我在处理 CoreData 中的对象 ID 时遇到问题。为方便起见,我使用 MagicalRecord 并具有 3 个上下文:私有队列工作上下文、UI 的主队列上下文和工作上下文的父级,以及作为主上下文父级的私有队列保存上下文。

我的目标是在工作上下文中创建一个对象,保存到持久存储中,将它的 objectID URL 保存到 NSUserDefaults,然后稍后能够使用 objectID 拉出 MO。但是,我发现保存对象的永久 ID 后发生了变化。

在下面的控制台输出中,您可以看到,在我请求永久 ID 后,我得到的值是“F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1”,但后来当我列出 CD 中的所有对象时那里唯一的对象的 ID 为“F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2”。 p1 vs p2,发生了什么?

代码:

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

      });
  }];

控制台输出:

  > +[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>)"
  )}

【问题讨论】:

    标签: core-data nsmanagedobject magicalrecord


    【解决方案1】:

    这可能是因为您使用的是嵌套上下文。

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

    输出:

    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>
    

    【讨论】:

    • 是的,这是我所期待的行为。也许它与嵌套上下文有关。
    【解决方案2】:

    简短的回答是,不要那样做 :)

    -objectID 在应用程序启动之间不可靠。在以下条件下保证唯一且可靠:

    1. 在应用程序的单个生命周期内
    2. 以其原始对象形式(不是 URL 或 NSString 形式)

    -objectID 视为要存储在持久存储之外的永久唯一标识符会经常让您失望。 Core Data 在对象的生命周期内多次更改-objectID 的底层细节。

    如果您需要一个外部可靠的唯一性,那么您需要自己创建一个。我通常建议将[[NSProcessInfo processInfo] globallyUniqueString] 添加到任何需要外部引用唯一的实体。 -awakeFromInsert 是这样做的好地方。

    【讨论】:

    • 非常感谢马库斯!所有文档中的“永久”一词确实具有欺骗性。我真的希望你能推出一个新版本的 CD 书,因为旧版本是 OOP。
    • 如果NSManagedObjectID 在应用程序启动之间返回NOisTemporaryID,你肯定在做一些粗略的事情。这对我来说非常有效,并且已经这样做了很长时间。
    • 它可以并且将会改变。 iCloud 改变了它,迁移改变了它,许多事件可以在启动之间改变它。它不应用作唯一标识符,这不是其预期目的。
    • 来自文档:“一个托管对象 ID 在单个应用程序和多个应用程序的托管对象上下文之间唯一标识相同的托管对象......对象 ID 可以转换为 URI 表示形式可以存档并在以后重新创建以引用给定的对象”。因此,我不同意您的说法,即其预期目的不是作为唯一标识符。错误在于实施和/或文档。而缺乏可靠的通用标识符是 Core Data 的一个巨大缺点。虽然我要补充一点,我很少看到它们发生变化。
    • 我建议针对文档提交雷达。在某些情况下,-objectID 确实会在启动之间发生变化,因此将其用作 uniqueID 是危险的。自从 Core Data 诞生以来,就一直缺少一个 uniqueID。就像那里的任何数据库一样;创建你自己的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多