合并到父/主上下文后,子上下文对象变为空
Posted
技术标签:
【中文标题】合并到父/主上下文后,子上下文对象变为空【英文标题】:Child context objects become empty after merge to parent/main context 【发布时间】:2014-02-11 22:45:57 【问题描述】:我正在使用 Core Data 开发一个多线程应用程序。具有讽刺意味的是,当我得知 Core Data 不是线程安全的时,我认为应用程序即将完成......因此,我现在添加多上下文而不是从 Xcode 模板获得的单上下文(并且有到目前为止一直在工作,真的,但我猜这更多的是运气而不是技能)
我正在尝试将 ios 5.0 方法与父/子上下文一起使用,这很适合我正在尝试做的事情,但是当我在子上下文中插入具有有效数据/属性的有效对象时,它们在父上下文中全部变为 nil 或 0(取决于属性类型)。
我注意到有类似的帖子,但没有任何答案;
Parent MOC get changes with empty data from child MOC
NSManagedObject values are correct, then incorrect when merging changes from parent to child NSManagedObjectContext
这里有一些代码可以理解这个想法;
我有一个 UI 用来“做事”的单例管理器,然后在任务完成时使用委托或回调。该管理器反过来有一个模型管理器来处理持久数据管理,还有一些其他的通信管理器来与 web/REST-API 等对话。
- (void) doSomeStuff:(NSString*)someParam
callbackObject:(NSObject*)object
onSuccess:(SEL)successSelector
onFailure:(SEL)failureSelector
//Kick as an async thread since we don't want to disturb the UI
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void)
//Ask model manager for a nice context to work with...
//NOTE; All contexts (private ones) are stored in a NSDictionary using currentThread as key, so that we always can access the "correct" context at anytime if we need to insert/delete/change anything in the context
NSManagedObjectContext* privateContext = [self.modelManager getManagedObjectContext];
//Perform stuff in block since our context is of NSPrivateQueueConcurrencyType
[privateContext performBlockAndWait:^
//... do the actual stuff, go online, talk to a REST-API, wait for things, which will eventually result in a new object being created
User* user = ...;
//Store object in model manger which is my abstraction of CoreData
//NOTE: the modelManager will get a reference to the currently used privateContext and use it to insert the object
[self.modelManager addUser:user];
//Save model!
[self.modelManager save];
];
//Trigger callback, if applicable
if (loggedInUserGuid && successSelector)
[object performSelectorOnMainThread:successSelector withObject:loggedInUserGuid waitUntilDone:NO];
);
modelManager 中的保存功能会考虑上下文 concurrencyType,并且在使用子/父上下文时会根据规范进行操作;
- (void) save
//Get current context...
NSManagedObjectContext* currentContext = [self getManagedObjectContext];
//If the current context has any changes...
if ([currentContext hasChanges])
//Changes detected! What kind of context is this?
switch (currentContext.concurrencyType)
case NSPrivateQueueConcurrencyType:
NSError* error = nil;
if (![currentContext save:&error])
abort();
if (self.mainManagedObjectContext hasChanges])
[self.mainManagedObjectContext performBlockAndWait:^
NSError *mainError;
if (![self.mainManagedObjectContext save:&mainError])
abort();
];
break;
....
通过在保存子上下文和父/主上下文之前和之后添加调试打印,我注意到插入的对象很好地存在于子上下文中,并且子上下文说“hasChanges == YES”,而正如预期的那样,主上下文说“hasChanges == NO”。
(entity: User; id: 0x10c06c8c0 <x-coredata:///User/tDFBBE194-44F9-44EC-B960-3E8E5374463318> ; data:
created = nil;
emailAddress = "wilton@millfjord.se";
firstName = Wilton;
guid = "2eaa77fa-0d2c-41b8-b965-c4dced6eb54a";
lastName = Millfjord;
nbrOfOfflineKeys = 5;
password = 123456;
)
主上下文随后也被保存,并且在保存之前查看它的注册对象,我们可以看到“hasChanges == YES”(预期在子对象将插入的对象保存/合并到它的父对象之后。但是,此外-- 所有参数和属性现在都是 nil 或 0,具体取决于属性类型...;
(entity: User; id: 0x10c06c8c0 <x-coredata:///User/tDFBBE194-44F9-44EC-B960-3E8E5374463318> ; data:
created = nil;
emailAddress = nil;
firstName = nil;
guid = nil;
lastName = nil;
nbrOfOfflineKeys = 0;
password = nil;
)
如您所见,ID 是相同的,因此它是“相同”的对象,但没有/重置内容。我已经尝试了“setMergePolicy”的所有各种组合,但没有效果。
我什至尝试过在 iOS 5.0 中添加 NSNotificationCentre 方法,其中我“mergeChangesFromContextDidSaveNotification”,我唯一可以验证的是 NSNotification 参数中的数据是有效的并且是“好的数据”,但主要上下文仍未正确更新。结果仍然是一个空对象。
期待您的想法和想法。
/马库斯
更新
创建新托管对象时使用的代码,使用主上下文,无论当时正在运行哪个线程/上下文执行块......
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.mainManagedObjectContext];
User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
对象 user 此后在没有上下文的情况下挂起,直到稍后我决定将其添加到托管上下文(使用我的 getManagedObjectContext 来获取正确/当前上下文)。
[[self getManagedObjectContext] insertObject:user];
【问题讨论】:
您是否尝试过使用 performBlock: 而不是 performBlockAndWait?查看您的问题和您链接的其他两个问题,我唯一注意到的是你们都在使用 performBlockAndWait。你有什么理由要等待吗?我在使用 CoreData 时遵循类似的模式,但我只使用 performBlock。 我尝试更改为 performBlock,但行为没有变化。我宁愿使用 performBlockAndWait 的原因是我希望管理器“doSomeStuff”做一些异步操作以避免干扰主线程。因此,我首先调度一个新的异步线程。一旦执行,该线程依次向模型管理器请求私有上下文。因此,我认为异步进程已经足够了,并且我们已经从主线程和 UI 干扰中分派,所以让我们等待块结束。 尽管如此,我想知道我基本上在这里嵌套一个主线程这一事实是否会干扰?我的意思是,主线程调用 doSomeStuff 调度了一个异步工作线程,而后者又调用了 performBlockAndWait ...?一个太多了?这种方法确实与参考示例不同,因为我根据正在运行的线程获取/创建私有上下文......我见过的大多数示例都明确地创建一个新的私有上下文,然后 performBlock 异步(来自主线程?) .只是大声思考......我还将“保存”机制作为一个函数而不是块内的内联代码......? 看看我不久前发布的这个问题***.com/questions/13571721/…。我链接到一篇文章,该文章基本上引导我弄清楚如何设置我的核心数据堆栈。另外我个人不喜欢你的 modelManager getManagedObjectContext 方法。我不能说这是否是您的问题,但对我来说,您使用线程作为键将上下文存储在字典中似乎很奇怪?好像有点矫枉过正。当您进入一个新线程时,只需生成一个上下文并将其父级设置为您的主线程上下文。只是抛出想法! 我同意...这种方法的原因是我不需要添加私有上下文作为参数并向下传递给模型管理器(从 performBlock 中),以便模型管理器将对象插入实际上下文时具有适当的上下文。另外,刚刚意识到当模型管理器创建一个新对象时,它使用主上下文。我用代码更新了帖子。谢谢链接,我将开始阅读;) 【参考方案1】:在尝试了一切之后,慢慢地将我现有的代码减少到什么都没有,只是一个没有函数或管理器的直接内联版本——只是为了确定何时我可以看到 mainContext 中的已注册对象在保存子上下文后,其实不是空元素……我终于找到了答案。
事实证明,我正在(故意)创建在创建时不受管理(置于上下文中)的对象。我的想法是,当我与 REST-API/后端通信时,我可以将 JSON 响应转换为对象(松散,没有上下文 - 尚未),然后与我已经存储在模型管理器中的内容进行比较,这样我就可以检测更改并通知用户这些更改...
因此,我做到了;
//Create object "hanging" so that we can add it to context alter on...
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:mainContext];
User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
...然后,当我决定这是新的东西和我想保留的东西时;
//Insert it
[privateContext insertObject:user];
但是,插入的对象在保存在子上下文中时,会自动合并到父上下文的主上下文中——清空!但是当我尝试将对象直接添加到私有上下文时,对象在合并过程中突然没有被清空;
//Create object AND insert into context now...
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:mainContext];
User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:privateContext];
我不明白为什么,真的,但就是这样。从子级的私有上下文合并到父级的主上下文时,合并对象被清空的原因。我宁愿事实并非如此,因为这将意味着我正在运行的“更改检测/通知”代码的大规模重组,但至少在 CoreData 方面我是线程安全的;)
【讨论】:
快速反思;我会说这是CoreData中的一个错误? Apple 在他们的文档中说自己创建 NSManagedObjects 而不将它们插入上下文是完全可行的——并且可以在稍后阶段使用上下文的 insertObject 函数添加对象......但是,我在这篇文章中的发现表明这样一个方法不能与将子上下文合并到父/主上下文的多上下文应用程序结合使用,因为对象在合并时被清空。有没有人有类似的结果?这可能是 iOS 错误,还是“只是我”? ;) 您找到解决方案了吗?我只是在和完全相同的东西打架。不得不维护两组数据模型(一组用于 json 存储,一组用于 coredata)听起来真的有点愚蠢——只是为了核心数据工作:(然而,这正是我正在做的事情,它是很痛苦。做这么简单的事情。 老实说,现在已经有一段时间了,但我认为当我停止使用迁移和合并设置时,我的问题就消失了。我开发的应用程序仍然运行良好,并且已针对 iOS 8、9 和 10 重新发布,在那段时间里我们没有遇到任何与 CoreData 相关的问题。以上是关于合并到父/主上下文后,子上下文对象变为空的主要内容,如果未能解决你的问题,请参考以下文章