在Cocoa/Cocoa Touch应用程序中,如何放置“Core Data Stack”?

67
在iPhone核心数据模板中,苹果公司将Core Data Stack放置在App Delegate中。
然而,我的初步倾向是将此代码移至其自己的类中,该类负责处理Core Data Stack的管理。
你通常会将此功能封装在自己的类中,还是将其保留在App Delegate中?
6个回答

39
总结:没有必要创建一个单例来管理Core Data堆栈;实际上这样做可能会产生反效果。
核心数据堆栈恰好是由应用程序委托创建的。然而,正如所有示例所示,堆栈(主要是托管对象上下文)不是直接从堆栈中检索出来的(*)。相反,上下文被传递给第一个视图控制器,并且从他们开始,上下文或托管对象从一个视图控制器传递到另一个视图控制器(如访问Core Data堆栈中所述)。这遵循了iPhone所有应用程序的基本模式:您将数据或模型控制器从一个视图控制器传递到另一个视图控制器。
在这里描述的单例的典型角色是模型控制器。使用Core Data,托管对象上下文已经是模型控制器。它还使您能够访问堆栈的其他部分(如果需要)。此外,在某些情况下(如文档中所述),您可能希望使用不同的上下文执行一组离散的操作。因此,视图控制器的适当货币单位通常是托管对象上下文,否则是托管对象。使用和传递一个管理堆栈的单例对象(从中检索上下文)通常在最好的情况下引入了不必要的间接级别,在最坏的情况下引入了不必要的应用程序刚性。
(*) 没有一个示例使用以下方法检索上下文:
[[UIApplication delegate] managedObjectContext];

2
在我最初开始使用Core Data时,不使用依赖注入绝对是一个糟糕的设计。最近,我采取了与您概述的相同方法。主要区别在于,我将Core Data堆栈代码放在NSManagedObjectContext的类别中,仅仅为了逻辑上将Core Data堆栈代码与AppDelegate分离。理论上,我可以像单例一样使用这个类别,但我选择不这样做,因为它会引入“应用程序刚性”,正如您所说的那样。此外,我还使用一些自定义代码来处理Core Data堆栈,这使我可以轻松地将此代码插入到新项目中。 - Corey Floyd
1
我赞同使用App Delegate来创建Core Data堆栈。我的根视图控制器是UITabBarController,但我不确定如何将上下文传递给该控制器对象,因为它位于MainWindow.xib中,我不知道如何为其分配指向ManagedObjectContext的指针。我想我会为此发布一个单独的问题。 - mvexel
1
那份苹果文档中提到:“当你创建一个视图控制器时,你需要传递它应该使用的上下文。”但我不知道具体如何实现。如果使用故事板,主视图控制器是通过故事板创建的,对吧?那么如何传递上下文呢? - Victor Engel
3
@VictorEngel,你找到了吗?如果视图控制器是由故事板创建的,上下文应该如何传递?令人沮丧的是,到处都有人说不要从应用程序委托获取它,但没有说一句话应该怎么做才能获得上下文。 - Madhu
如果您查看苹果的模板,视图控制器具有上下文的可变属性。第一个可以使用上下文的视图控制器在didFinishLaunchingWithOptions中设置它。从那时起,它将传递给每个后续的视图控制器。这也在文档中有所涵盖。 - quellish

28

我有一个单例类,用于管理核心数据,而不是将其留在应用程序委托中。我不想在应用程序委托类中添加可能需要的方法,如获取特定对象等,以免使它变得混乱。


1
听起来对我很实用。我很惊讶苹果在应用程序委托中包含它。 - Corey Floyd
4
他们可能这样做是因为他们想展示如何做,而这个位置是他们认为比较方便的地方,因为应用代理已经类似于一个单例模式了。 - Daniel
3
拥有一个单例核心数据控制器对象是非常合理的。我们对它进行了抽象,以便在每个项目中都可以重复使用。+1 - stigi
1
我目前也在为Core Data堆栈使用单例类。我认为它的作用类似于通知中心或共享用户默认设置,您可以调用[[DatabaseController sharedDatabaseController] writableManagedObjectContext]在需要时获取特定上下文。回到应用程序委托以获取堆栈似乎很笨拙。 - Brad Larson
我在考虑使用单例,尽管我尽量避免使用它们。当然,应用程序委托基本上就是一个单例,只是它有一个笨重的方法调用。我还认为,拥有一个可以轻松添加到任何应用程序中的类来处理核心数据会更符合面向对象编程的思想,而不是将方法/实例变量添加到应用程序委托中。到目前为止,“两边”的答案都很好。 - Corey Floyd
2
我同意(a)有一个通用的核心数据管理类,可以更轻松地放入项目中(特别是已经存在的项目),以及(b)它总是在AppDelegate中作为示例的原因是他们试图尽可能减少非示例代码 - 所以为什么要制作一个完整的单例,当AppDelegate自由地表现出这种方式(就代码长度而言)。我会把它放在一个单例中,这样只有处理Core Data的类才与单例有任何联系,这也意味着较少的类需要包括App Delegate头文件。 - Kendall Helmstetter Gelner

11

我将核心数据逻辑留在App delegate中,原因如下:

1)我没有看到将此代码移动到其他类中的任何真正优势:代理的概念通过由App delegate处理核心数据逻辑已经完美实现,因为核心数据模型实际上是应用程序的基本部分;

2)在我所见过的所有示例代码中,包括Apple的示例代码,核心数据都由App delegate处理;

3)即使在Core Data书籍中,让App delegate处理与核心数据相关的代码也是常见做法;

4)就个人而言,我并不认为使用专门的类来处理核心数据会提高可读性或其他方面的优点,但这是一种个人喜好,我不会在这里争论哪种方法是最佳的。对我来说,保持功能的同时保持简单很重要。


通常我也会在应用委托中看到 Core Data 堆栈。然而,我看的代码通常是为了说明目的而创建的。实际实现某些东西的方法有时与这些示例不同。我不想没有充分理由地盲目跟随苹果的示例代码。我倾向于认为,在选择使用哪种方式时,个人喜好会发挥重要作用,并且每种方式都有一些优点。 - Corey Floyd
3
我认为第二和第三个论点是因为在教程或示例中,您试图尽可能地减少与您要介绍的内容无关的任何代码 - 因此,实现 Singleton 的机制会对本应该是简单示例的代码增加太多开销。我不喜欢将这些内容保留在应用程序委托中的原因是,它增加了必须了解应用程序委托的内容数量。 - Kendall Helmstetter Gelner
委托的概念通过应用程序代理处理核心数据逻辑完美地实现,因为核心数据模型实际上是您的应用程序的基本组成部分。不,UIApplication并没有将Core Data功能的任何责任委托给其代理。您可以决定持久存储是否是应用程序级别的问题,但它并不是UIApplicationDelegate的一部分。 - quellish

10

在你的情况下,我会问自己一个问题:“Core Data堆栈归属于谁?”数据本身确实是应用程序的范畴,不是吗?(参见Mac上的Core Data,在那里您可能拥有能够同时使用多个文档的应用程序,因此Core Data堆栈属于每个文档。)

在任何Cocoa/Cocoa Touch应用程序中,应用委托通常是自定义应用程序行为的首选方法,因此这是Core Data堆栈的自然位置。

现在,我怀疑你遇到的问题是经常写诸如以下内容感觉不对:

NSManagedObjectContext *context = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];

在这种情况下,我通常会编写如下所示的函数(而不是方法):

NSManagedObjectContext *UIAppManagedObjectContext() {
    return [*(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}

我为 NSPersistentStoreCoordinatorNSManagedObjectModel编写了类似的函数。 我把它们都放在应用程序委托的.h/.m文件中,因为这些也是应用程序级别的对象。


2
很有趣。那正是我不喜欢的代码片段。我不喜欢为文件存储信息而去运行到应用程序委托。这感觉“不对”。这让我质疑其他开发者如何处理这种情况。 - Corey Floyd
1
第一个代码片段感觉不对的原因是它是一种代码异味。将其封装在一个方便的函数中只是香水。更直接的方法是将上下文传递给需要它的对象(主要使用属性注入)。 - Luke Redpath
2
你不应该像这样从应用程序委托获取上下文。你应该像苹果的所有示例中所示,从一个视图控制器传递上下文到下一个视图控制器。 - mmalc
更多支持不使用应用程序委托来分发数据模型的证据:http://www.hollance.com/2012/02/dont-abuse-the-app-delegate/ - Joey J

6

我会在新答案中列出这个问题的解决方法。(我放弃了以前的FJSCoreDataStack类,采用了新的方法)

我的新方法是在NSManagedObjectContext上使用一个分类。我添加了以下类方法:

+ (NSManagedObjectContext *)defaultManagedObjectContext;
+ (NSManagedObjectContext *)scratchpadManagedObjectContext;
+ (NSManagedObjectModel *)managedObjectModel;
+ (NSPersistentStoreCoordinator *)persistentStoreCoordinator;
+ (NSString *)applicationDocumentsDirectory;

这样可以使我的应用程序委托保持干净,同时如果我选择使用它,则可以获得单例访问。然而,我仍然从应用程序委托使用依赖注入(正如mmalc所说,它会使我的代码不够灵活)。我只是将所有的“Core Data Stack”代码移动到了NSManagedObjectCOntext类别中。
我喜欢传递引用,特别是因为我有一个很好的“scratchpad context”方法。这使得我的视图控制器灵活,因为我没有将它们提交给“defaultManagedObjectContext”。
此外,在iPhone世界中与本次讨论相关的问题(可能对你的架构有影响): NSFetchedResultsController和构建NSFetchRequest

4

我赞成将应用程序委托知道模型从哪里开始,并且让模型知道托管对象上下文在哪里。对于我来说,模型的Core Data-"ness"似乎是模型的实现细节,控制器类(如应用程序委托)应该只询问“给我关于模型的这些信息”,而模型应该知道如何回答这个问题。因此,通过控制器对象可用的Core Data对象似乎是一个泄漏的抽象。


在iPhone开发中,使用和配置NSFetchedResultsControllers已经成为一个问题。你也可以让你的“模型”现在知道如何配置和返回NSFetcheResultsControllers,但是似乎模型类会变得有些臃肿。我觉得NSFetchedResultsControllers使控制器和模型代码之间的界限变得模糊(不一定是坏事)。最近我把这个想法和其他一些想法加入到我的新配置中了(添加了新答案)。 - Corey Floyd
我同意@Graham的观点,这也是我处理的方式。你的UIViewControllers不应该涉及到NSManagedObjectContext,它们只需要与模型交互并请求所需的内容即可。获取信息的机制对于我的视图控制器来说并不重要。 - kubi

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