如何在视图控制器中获取managedObjectContext而不是从appDelegate获取?

21
最近我知道了“您真的不应该调用AppDelegate来获取托管对象上下文”。 苹果还在他们的文档这里中提出了这个建议。 大致意思是:

视图控制器通常不应从全局对象(例如应用程序委托)中检索上下文 - 这使应用程序架构变得严格。 视图控制器也不应为自己使用创建上下文(除非它是嵌套上下文)。 这可能意味着使用控制器上下文执行的操作不会在其他上下文中注册,因此不同的视图控制器将具有数据的不同视角。

此外,他们还提到了一些其他获取上下文的方法。 到目前为止,我无法弄清楚他们试图说什么。 有人可以请解释一下这个问题吗? 支持语句的任何代码片段都将非常受欢迎。 编辑
有时候,从应用程序、文档或视图控制器以外的其他地方检索上下文更加容易或更加合适。在基于 Core Data 的应用程序中,您可能会使用几个对象来保留对托管对象上下文的引用。托管对象本身有对其自己上下文的引用,支持 Core Data 的各种控制器对象(如 OS X 中的 NSArrayController 和 NSObjectController 以及 iOS 中的 NSFetchedResultsController)也是如此。
从这些对象之一检索上下文的优点在于,如果您重新设计应用程序(例如利用多个上下文),则代码很可能仍然有效。例如,如果您有一个托管对象,并且想要创建一个与其相关的新托管对象,则可以向原始对象请求其托管对象上下文并使用该上下文创建新对象。这将确保您创建的新对象与原始对象位于同一上下文中。
这是苹果文档的一部分,它解释了从不同对象中检索上下文的好处。

@uliwitness 我很想知道,为什么不用 appDelegate,而另一个单例是可以的? - Prince Agrawal
因为应用委托被命名为 "应用委托"。它旨在处理与整个应用程序相关的事情以及如何从外部(dock 菜单、打开文件等)感知应用程序。这是针对此特定应用程序的代码,根据设计不可重用。其他单例与此特定应用程序无关。另外,您的单例具有更精确的名称。如果您将此类内容放入应用委托中,它会收集垃圾,因为所有单例都是 "针对整个应用程序" 的。 - uliwitness
不建议使用应用程序委托的原因可能是,如果您需要引入新的MOC,则设置允许更多“模块化”代码。这更符合面向对象的架构。 - Walter Martin Vargas-Pena
在控制器之间传递相同的上下文的缺点是,如果同一实体在两个不同的位置被修改,则必须管理合并冲突。这通常在iOS中不是问题,因为通常一次只能看到一个视图控制器。 - Walter Martin Vargas-Pena
我反对使用应用程序委托的理由有点复杂,不适合在评论中讨论,所以我写了一篇博客:http://orangejuiceliberationfront.com/the-universal-catch-all-singleton/ - uliwitness
显示剩余2条评论
2个回答

40
被称为依赖注入。基本上,调用者/构造函数应该将 NSManagedObjectContext 设置到被调用/构造的对象中。
在你的 AppDelegate 中,应该将NSManagedObjectContext设置到与UIWindow相关联的rootViewController中。
然后,你的rootViewController应该将NSManagedObjectContext设置到下一个视图控制器中,以此类推。
如何实现呢?只需要在视图控制器类中进行简单的属性声明,然后调用方使用即可。
[nextViewController setManagedObjectContext:[self managedObjectContext]];

有些人可能会建议使用单例,但那是另一个最好避免的深坑。

更新

依赖注入是最佳方法。

这是苹果设计的方法。另一种选择涉及某种形式的单例:AppDelegate或另一个单例。

“在控制器之间传递相同的上下文的缺点是,如果在两个不同的位置修改同一实体,则必须管理合并冲突。”

这是完全不同的问题,它不会通过使用多个NSManagedObjectContext实例来解决。事实上,多个实例会使情况变得更糟,并确保出现合并冲突。

在那种情况下,您的视图控制器应该监听托管对象中的更改并对其做出反应。使其无法同时在UI中的两个位置更新。用户无法同时关注两个位置,因此第二个位置将实时更新。

那就是该问题的正确答案。

将两个实体都放入同一个上下文中将确保其正常工作。多个上下文会导致内存中有两个具有相同数据的对象,而没有办法在不保存到上下文的情况下注意到更改。

但是,如果您有视图控制器在没有用户干预的情况下修改数据,则存在单独的问题。视图控制器用于用户修改或查看数据。它们是任何类型的后台数据处理的地方。

如果在导入情况下,则与您提问的问题不同。在这种情况下,您(应该)使用多个线程(UI线程,导入线程),并且您必须每个线程至少拥有一个上下文。

在这种情况下,您确实面临合并冲突的风险,并且需要针对发生的情况进行编码。第一步是更改NSManagedObjectContext实例的合并策略。

更新

我怀疑您误读了那份文档。

那是在描述从NSManagedObject实例中获取NSManagedObjectContext的能力。这是绝对有用的。例如,考虑具有添加或编辑对象功能的视图控制器。通过仅推送NSManagedObject到视图控制器,您可以控制并决定该视图控制器将要操作的内容。接收视图控制器知道它需要允许对接收到的NSManagedObject进行编辑。它不关心它正在使用哪个NSManagedObjectContext。它可以使用主要的,也可以使用子类,也可以在单元测试中隔离,它不需要知道或关心。它只是显示从手头得到的NSManagedObject的数据,并在用户选择保存编辑时保存相关的NSManagedObjectContext

该文档并没有建议为您的NSManagedObjectContext创建某个通用位置(即单例)。它建议如果您有另一种访问与NSManagedObject关联的NSManagedObjectContext的方法,则这样做是可以的,并且绝对有意义。


我了解这种方法,但是对此得到了褒贬不一的回应。有这样一句话:“在控制器之间传递相同的上下文的缺点是,如果同一个实体在两个不同的地方被修改,你必须管理合并冲突。” 我在某处读到提问中提到的方法比你提到的方法更好。 我正在尝试回想那篇文章。所以,不确定你的方法是否真的是一个好的方法。希望能有更好的解释和替代方案。对于替代方案表示赞同,但还没有被说服..抱歉。 - Prince Agrawal
请查看我的问题的编辑部分。它是否建议使用其他更好的方法来完成它? - Prince Agrawal
太棒了...非常感谢。你给了我清晰的画面..已接受 :) - Prince Agrawal
我已经为此苦苦挣扎了很长时间,谢谢! - larod

0

对于获取托管对象上下文,单例模式是我最喜欢的方法。这真的取决于您的应用程序的复杂性,但在我的情况下,我通常保留一个托管对象上下文,并在需要进行更改时使用临时嵌套上下文。

通过使用基于单例的“DataManager”类,其中包含所有核心数据初始化方法,并公开引用托管对象模型和上下文,我可以通过导入我的“DataManager.h”类并调用单例来访问数据:

// I have a method to create an object that requires the Manage Object Context, so I call it from the DataManager singleton
SomeObject *newObject = [SomeObject createObjectInContext:[[DataManager sharedInstance] managedObjectContext]];

// I have a method in DataManager to save the context
[[DataManager sharedInstance] saveContext];

那其实是简化版。我通常使用嵌套的托管对象上下文,这样我的主要托管对象上下文在添加或修改托管对象之前不会被修改,直到用户确认。所有这些复杂性都可以包含在“DataManager”类中。

这个话题有点偏离,但是如果您需要了解更多关于嵌套上下文的知识:当我没有使用嵌套上下文对主托管对象上下文进行更改时,我遇到了严重的问题。尽管其中一部分我不是很理解,但本文帮助我理解和实现嵌套上下文:

http://www.cocoanetics.com/2012/07/multi-context-coredata/


将您的Core Data代码(堆栈,迁移等)放入单个处理程序中是一个很好的建议。但将其作为单例不是。使用单例,您无法进行单元测试,无法直接控制要推入视图控制器的MOC以及其他问题。 - Marcus S. Zarra
我目前还没有使用单元测试(这在我的学习清单上),但是关于单例方法,不是只有一个MOC可以从任何给定的VC中调用吗?我不想在这个问题中离题,但我想了解你关于“无法直接控制哪个MOC被推送”的说法。只有一个MOC,如果需要更改,我会创建一个临时嵌套的MOC,然后将更改保存回主MOC或放弃临时MOC。我从来没有遇到过这种方法的问题,但这并不意味着它们不存在... - Paul Bonneville
以插入/编辑视图为例。在编辑时,我可以从主MOC中推送MO。在插入时,我可以创建一个新的MOC,创建与之关联的MO并将该MO发送到VC。VC不需要关心。对于测试,我可以模拟MOC并测试与我的VC /网络层接口等,并获得已知答案。单例使所有这些变得更加困难,只是为了避免依赖注入。单例最终会让您陷入不必要的困境,实际上是一种应该避免的hack。Google搜索“单例是邪恶的”并阅读 :) - Marcus S. Zarra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接