我正在努力使NSManagedObjectContext获得核心数据。如果在创建新项目时选中了“核心数据”复选框,则Xcode 10.1将提供大量样板。但是我发现如何为每个视图控制器设置当前上下文有些困惑。我认为我有更好的方法,正在寻求建议以确认或使我回到正确的轨道上。
例如,在样板AppDelegate代码中,didFinishLaunchingWithOptions像这样向MasterViewController提供了上下文:
let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController
let controller = masterNavigationController.topViewController as! MasterViewController
controller.managedObjectContext = self.persistentContainer.viewContex
在MasterViewContoller中,上下文的首次使用是从fetchedResultsController中获取上下文的,并且有代码保存提供的上下文,即使AppDelegate已经具有saveContext()函数也可以执行相同的操作:
@objc
func insertNewObject(_ sender: Any) {
let context = self.fetchedResultsController.managedObjectContext
let newEvent = Event(context: context)
// If appropriate, configure the new managed object.
newEvent.timestamp = Date()
// Save the context.
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
在我的具有多个视图控制器的应用中,尝试在每个需要的地方重新声明或移交上下文时犯了错误,因此不得不应对由于无意间绕过多个上下文而造成的错误。
所以我的问题是:我犯错了,还是以下方法有缺点:
1)将AppDelegate设为单例:
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
static let shared = AppDelegate()
…
2)在需要的每个类中,总是这样定义上下文(我假设我只需要一个):
let context = AppDelegate.shared.persistentContainer.viewContext
3)每当需要保存上下文时,请执行以下操作:
AppDelegate.shared.saveContext()
这似乎更简单,更清楚并且更不容易出错,并且似乎可以在我的实现中使用。是否有我没有看到的问题?
答案 0 :(得分:0)
说实话,对于初学者来说,Apple的示例/模板始终是不好的示例,因为它们仅显示一件事,并且在休息时会“砍”(例如,强制展开所有内容)。而且初学者倾向于只复制这种方法。
免责声明:我谈论的是中型应用程序。您总是可以在小型应用程序中违反此规则和建议,因为不使用它们可能会更容易并导致应用程序更简单。
在99%的情况下,您不应自行实例化AppDelegate
。它通过UIApplication
/ @UIApplicationMain
注释为您处理。
AppDelegate
已经单例,因为每个应用程序在整个生命周期中只有一个委托。您可以通过UIApplication.shared.delegate as? AppDelegate
访问它。
但是您不应该这样做。 AppDelegate
通过为系统和您的代码之间的通信提供入口点,在每个应用程序中扮演特定角色,并且您不应该在其中添加其他角色(例如处理数据库)。在大多数情况下,代码气味和体系结构不良的迹象都可以在代码库中的某个位置访问它。
数据库访问是善用Singleton模式的几个示例之一。但是,您应该使用单独的服务,而不是使用AppDelegate
,该服务仅负责处理与核心数据的通信(例如,创建和处理堆栈,发送查询等)。
所以CoreDataService
是可行的方式。
使用单例并不意味着您可以通过键入Singleton.shared
在任何地方访问它。这将大大降低组件的可测试性,并使它们与单例高度耦合。
相反,您应该阅读Dependency injection principle并注入单身人士。例如:
class MyViewController: UIViewController {
let dataBaseManager: CoreDataService
init(with dataBaseManager: CoreDataService) {
self.dataBaseManager = dataBaseManager
super.init(nibName: nil, bundle: nil)
}
}
理想情况下,您应该走得更远SOLID并仅向管制员提供其真正需要的东西:
protocol EventsProvider {
func getEvents(with callback: [Event] -> Void)
}
extension CoreDataService: EventsProvider {
func getEvents(with callback: [Event] -> Void) {
// your core data query here
}
}
class MyViewController: UIViewController {
let eventsProvider: EventsProvider
init(with eventsProvider: EventsProvider) {
self.eventsProvider = eventsProvider
super.init(nibName: nil, bundle: nil)
}
}
let vc = MyViewController(with: CoreDataService.shared)
拥有多个NSManagedObjectContext
可以方便并提高性能,但前提是您知道如何使用它们。
这是更高级的主题,因此您现在可以忽略它。
您可以在Core Data Programming Guide
答案 1 :(得分:0)
一个有趣的实验是利用响应者链并将insertNewObject方法移至应用程序委托,并将其更改为调用[self saveContext]。然后让添加按钮将动作发送到nil而不是self。或在情节提要中,拖动条形按钮项,然后将其从动作拖到第一个响应者图标(它位于视图控制器的顶部,也位于左侧栏中),然后选择insertNewObject并尝试一下!