iOS CoreData+MoGenerator:如何仅在使用嵌套上下文时初始化托管对象一次?

Posted

技术标签:

【中文标题】iOS CoreData+MoGenerator:如何仅在使用嵌套上下文时初始化托管对象一次?【英文标题】:iOS CoreData+MoGenerator: How do I initialize a Managed Object once only when I am using nested contexts? 【发布时间】:2013-10-02 18:49:10 【问题描述】:

我正在使用 mogenerator 从具有 TestPerson 托管对象的模型生成代码。 TestPerson 继承自抽象对象 TLSyncParent。在 TLSyncParent 我有代码:

- (void) awakeFromInsert

    [super awakeFromInsert];
    QNSLOG(@"%@\n%@", self.managedObjectContext, self.description);
    if (self.syncStatus == nil) 
        self.syncStatusValue = SYNCSTATUS_NEW;
        self.tempObjectPID = [self generateUUID];
        QNSLOG(@"After init values\n%@", self.description);
    

我在 childMOC 中创建 TestPerson 对象,其父对象是 mainMOC,其父对象是 rootMOC。 awakeFromInsert 按预期运行并进行初始化更改。当我将 childMOC 保存到 mainMOC 时,awakeFromInsert 再次运行。从文档中我不会想到这一点,但有一些模棱两可。从文档中,“您通常使用此方法来初始化特殊的默认属性值。此方法在对象的生命周期中仅调用一次。”真正的问题是,当 awakeFromInsert 在 mainMOC 中运行时,在 childMOC 中所做的初始化更改不存在。 awakeFromInsert 显然是在保存实际发生之前运行的。

2013-10-02 11:22:45.510_xctest[21631:303] TestPerson -awakeFromInsert <NSManagedObjectContext: 0xd684780>
<TestPerson: 0xd6863b0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: 
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 0;
    tempObjectPID = nil;
    updatedAt = nil;
)
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert After init values
<TestPerson: 0xd6863b0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: 
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 4;
    tempObjectPID = "7AB46623-C597-4167-B189-E3AAD24954DE";
    updatedAt = nil;
)
2013-10-02 11:22:45.511_xctest[21631:303] CoreDataController -saveChildContext: Saving Child MOC
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert <NSManagedObjectContext: 0xd682180>
<TestPerson: 0xd68fce0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: 
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 0;
    tempObjectPID = nil;
    updatedAt = nil;
)
2013-10-02 11:22:45.511_xctest[21631:303] TestPerson -awakeFromInsert After init values
<TestPerson: 0xd68fce0> (entity: TestPerson; id: 0xd684ed0 <x-coredata:///TestPerson/t02B71E0D-AE3F-4605-8AC7-638AE072F2302> ; data: 
    dept = nil;
    job = nil;
    objectPID = nil;
    personName = nil;
    syncStatus = 4;
    tempObjectPID = "B799AFDA-3514-445F-BB6F-E4FE836C4F9D";
    updatedAt = nil;
)

在使用 MoGenerator 结构时初始化托管对象的正确位置是什么?

【问题讨论】:

tempObjectPID 的价值在哪里?它在第一条和第二条日志语句之间发生变化,但没有迹象表明它已被分配。 抱歉误导了您。我从发布的文本中删除了“无关”的部分,并且走得太远了。 tempObjectPID 在 awakeFromInsert 代码中分配。我会纠正的。我没有提到的其他一点是,当调用第一个 awakeFromInsert 时,syncStatus 为 0。我希望它为零。尽管尝试了很多,我还是无法弄清楚为什么。 其他信息,rootMOC是Private并发类型,mainMOC和childMOC是Main Queue并发类型。 【参考方案1】:

awakeFromInsert 上的文档有些过时,并没有反映嵌套上下文的实际情况。当它说方法是

当接收器首次插入托管对象上下文时,由 Core Data 框架自动调用。

实际上应该说类似“..first 插入 any 托管对象上下文”,因为(正如您所发现的)这种情况在嵌套上下文中不止一次发生。确实,awakeFromInsert 的概念在使用嵌套上下文时有点过时了。该方法显然是在旧的非嵌套时代设计的,尚未适应。

有几种方法可以解决这个问题。一种是简单的运行时检查,您可以在其中执行以下操作:

if ([[self managedObjectContext] parentContext] != nil) 
    // Set default values here

此代码仅在当前上下文是某个其他上下文的子上下文时运行。该方法仍然针对父上下文运行,但您跳过了默认值设置器。如果你只嵌套一层,那就没问题了,即一个父级有一个或多个子上下文,但没有父级的“孙子”上下文。如果您添加了另一个嵌套级别,您就回到了开始的位置。

另一种选择(也是我通常更喜欢的选择)是将默认值代码移动到单独的方法中,然后根本不使用awakeFromInsert。也就是说,创建一个名为setDefaultValues 之类的方法,在您的情况下,该方法设置syncStatusValuetempObjectPID 的值。 在您首次创建新实例后立即调用此方法,不要在其他任何地方调用。由于它永远不会自动调用,因此代码永远不会运行,除非您告诉它运行。

【讨论】:

谢谢。我认为可能是文档已过时。这在苹果文档中太常见了,恕我直言。作为 CoreData 的一部分,我真的很惊讶没有提供更好的解决方案。 另一个很好的讨论here【参考方案2】:

我很确定 Mogenerator 不会改变您创建托管对象的方式,而只会将实际托管对象类移动到带有“_”前缀的机器生成的文件中,并创建这些托管对象的子类来放置您的所有自定义逻辑,以便在您重新生成托管对象类时它不会丢失。

【讨论】:

我相信你是对的。我包含了有关 MoGen 的信息,以防它因我还不明白的原因而相关。【参考方案3】:

好的,感谢 Tom Herrington,我找到了一个非常好的方法来做到这一点。它似乎以最少的麻烦做我想做的事。它与 MoGenerator 结构完美契合。我已经使用 initWithMO​​C 方法在 NSManagedObject 上创建了一个类别。我添加了对方法 awakeFromCreate 的调用并提供了默认实现。您只需以与覆盖 awakeFromInsert 相同的方式覆盖 awakeFromCreate。唯一的要求是您始终使用 initWithMO​​C 方法创建 MO。

@implementation NSManagedObject (CoreDataController)

+ (NSManagedObject*) initWithMOC: (NSManagedObjectContext*) context

    NSManagedObject* mo = (NSManagedObject*)
            [NSEntityDescription insertNewObjectForEntityForName: NSStringFromClass(self)
                                          inManagedObjectContext: context];

    [mo awakeFromCreate];
    return mo;


- (void) awakeFromCreate

    return;

【讨论】:

无意冒犯。我只是打算将此标记为“已接受”,因为它包含作为最终解决方案的代码。所以不会让我这样做 2 天。 逻辑对我来说似乎很不错。当将来有人带着同样的问题来到这里时,我想让她注意解决问题的代码。她也应该阅读您的文章以了解其中的细微差别,但不必继续前进。

以上是关于iOS CoreData+MoGenerator:如何仅在使用嵌套上下文时初始化托管对象一次?的主要内容,如果未能解决你的问题,请参考以下文章

CoreData + mogenerator - 如何防止中间数据模型中的`setValue(forKey :)`引用最终数据模型中实体的'人类'类?

XCode 4.6 中的 Mogenerator 和 ARC

将 mogenerator 与 Core Data 实体一起使用会导致保存数据存储时出错

MagicalRecord saveWithBlock 崩溃

Plain Core Data vs Core Data + Magical Record

从 CoreData 关系中获取错误实体的对象