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
之类的方法,在您的情况下,该方法设置syncStatusValue
和tempObjectPID
的值。 在您首次创建新实例后立即调用此方法,不要在其他任何地方调用。由于它永远不会自动调用,因此代码永远不会运行,除非您告诉它运行。
【讨论】:
谢谢。我认为可能是文档已过时。这在苹果文档中太常见了,恕我直言。作为 CoreData 的一部分,我真的很惊讶没有提供更好的解决方案。 另一个很好的讨论here【参考方案2】:我很确定 Mogenerator 不会改变您创建托管对象的方式,而只会将实际托管对象类移动到带有“_”前缀的机器生成的文件中,并创建这些托管对象的子类来放置您的所有自定义逻辑,以便在您重新生成托管对象类时它不会丢失。
【讨论】:
我相信你是对的。我包含了有关 MoGen 的信息,以防它因我还不明白的原因而相关。【参考方案3】:好的,感谢 Tom Herrington,我找到了一个非常好的方法来做到这一点。它似乎以最少的麻烦做我想做的事。它与 MoGenerator 结构完美契合。我已经使用 initWithMOC 方法在 NSManagedObject 上创建了一个类别。我添加了对方法 awakeFromCreate 的调用并提供了默认实现。您只需以与覆盖 awakeFromInsert 相同的方式覆盖 awakeFromCreate。唯一的要求是您始终使用 initWithMOC 方法创建 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 崩溃