【问题标题】:Core Data Best Practices - Am I utilizing C.D. Correctly and Efficiently? - iOS Swift核心数据最佳实践 - 我在使用 C.D.正确有效? - iOS 斯威夫特
【发布时间】:2017-06-18 10:47:08
【问题描述】:

我目前如何使用核心数据:

我的应用程序加载:在此页面上,我创建了一个托管对象上下文,获取托管对象,然后加载/显示它们。这是一个表格视图,所以我允许删除。为了删除,我创建了一个托管对象上下文并删除了托管对象并重新加载了 tableview。在我的整个应用程序中都使用了相同的方法,还有其他操作,例如更新等。 重点是为每个操作创建一个新的托管对象上下文。

我目前对 Core Data 的理解是,Managed Object Context 有点像一个队列,里面装满了动作。托管对象是被修改并放入队列以执行操作的项目。按照这个逻辑,整个app不应该只有一个队列吗?

主要问题:

我需要在每个操作之前创建一个托管对象上下文吗?或者我可以在应用程序委托中创建一个托管对象上下文说完成启动了吗?并在整个应用程序中使用它?

编辑:

对于未来的观众(如果我理解所提供的答案),我的代码现在通常如下所示:

  1. 我有一个 Core Data 类,在这个函数中创建了一个 ManagedObjectContext
  2. 这个创建 MOC 的函数在应用委托中调用,成功后返回 MOC,然后我将其存储在单例中。
  3. 在整个应用程序中,由于我需要对核心数据对象进行更改,因此我执行以下操作:

    if let managedContext = ShareData.sharedInstance.managedObjectContext { // DO STUFF, update, delete, create ect ect ect }

编辑 2:我可能误解了“不要在应用程序委托中创建 MOC”,目前正在研究这个并试图学习创建 MOC 的最佳位置。如果您可以启动应用程序的每个视图都需要 MOC,那么在其他地方创建一个似乎很乏味。

【问题讨论】:

    标签: ios swift core-data nsmanagedobject nsmanagedobjectcontext


    【解决方案1】:

    您实际上应该只需要在应用的整个生命周期中创建一个核心数据堆栈。 (除大多数应用外,也有例外)。

    我目前正在使用JSQCoreDataKit 来管理堆栈的创建和保存上下文。这绝对值得一看。

    处理核心数据的正常方法类似于...

    1. 在应用启动时创建核心数据堆栈。通常通过单例访问(不在 APP DELEGATE 中)。

    2. 要从核心数据中读取数据,请从核心数据堆栈中获取mainContext,并在此mainContext 上执行提取。

    3. 对于回写(添加、更新、删除)数据,您可以使用mainContext,但也可以从核心数据堆栈中获取backgroundContextchildContext。在上下文的perform 块内执行更新和saveContext。 (这将合并对主要上下文的更改以供您阅读)。

    这应该涵盖了您想要做的大部分事情。

    看看 JSQCoreDataKit。它使托管对象上下文的创建变得更加简单。

    编辑以澄清第 1 点

    将东西放入 AppDelegate 是一种非常笨拙且懒惰的方式来全面获取数据。 AppDelegate 是一个单例,因此它似乎是放置它的理想场所。但是随着你添加越来越多,突然之间你就有了一个巨大的应用程序委托来驱动你的整个应用程序。

    使用单一职责原则,您的应用代理应该做一件事......成为您应用的代理。它应该响应应用程序状态变化等......

    我忘了添加...如果您为您的应用创建第二个目标(比如 TVOS 目标)。它不会使用相同的 AppDelegate。如果您所有的 CoreData 代码(和其他代码)都在 AppDelegate 中,那么 TVOS 应用程序将无法访问它。将其放在两个应用程序都可以访问的另一个类中意味着两个应用程序可以共享您用于 CoreData(等)的代码。

    创建另一个文件来保存您的核心数据堆栈并在您第一次需要访问核心数据时启动它非常容易。 (不一定来自 AppDelegate,但首先需要进行读/写)。

    RE 将核心数据堆栈设置放置在初始视图控制器中。你可以这样做。然后,您会遇到如何从应用程序中的每个其他视图控制器访问该核心数据堆栈的问题。您可以将初始视图控制器设为单例(不要这样做),也可以传递堆栈。

    这两种方法都可以完成,但 CoreData 本质上是单例的。您手机的光盘上只有一组数据。所以在这里创建一个单例并不是一件坏事。

    如果您确实制作了一个单例,那么就让它纯粹成为一个核心数据堆栈单例。我有一个叫CoreDataStackManager。它所做的只是持有coreDataStack 属性。

    【讨论】:

    • 谢谢!您能否澄清第一步以及为什么您应该在应用程序委托中创建核心数据堆栈 -> 然后放置在单例中?在初始视图控制器 VDL 中是否更合适?
    • @Jerland2 编辑后添加了为什么不使用 AppDelegate 的解释:D
    • 谢谢!非常有帮助,仍然尝试正确理解 Core Data 提供的大部分功能以及最有效的使用方式,这很有帮助!
    • 你应该只创建一个 NSManagedObjectContext 的说法是错误的。 NSManagedObjectContext 不是线程安全的!您有一个在主线程上使用的主上下文(主要用于读取和快速小型写入),但是如果您从后端获取大量数据,则应将其保存在后台线程中,使用另一个 NSManagedObjectContext 指向到同一家店。 developer.apple.com/reference/coredata/nsmanagedobjectcontext
    • @hybridcattt 已修复。谢谢
    【解决方案2】:

    每次您想对数据库进行一些操作时,都不需要创建管理器对象上下文。最佳实践是创建一个包含 MOC 的类,您可以在其中集中所有可能的数据库操作。这个类将是你的数据访问管理器

    这是一个你可能看起来像的例子:

    import Foundation
    import CoreData
    enum CommitError: Error {
        case failureToSave(error:Error?)
    }
    class CoreDataManager: NSObject {
        var managedObjectContext: NSManagedObjectContext
        override init() {
            // This resource is the same name as your xcdatamodeld contained in your project.
        guard let modelURL = Bundle.main.url(forResource: "YOUR_DATABASE_NAME", withExtension:"momd") else {
            fatalError("Error loading model from bundle")
        }
        // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
        guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
            fatalError("Error initializing mom from: \(modelURL)")
        }
        let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
        managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = psc
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let docURL = urls[urls.endIndex-1]
        /* The directory the application uses to store the Core Data store file.
         This code uses a file named "DataModel.sqlite" in the application's documents directory.
         */
        let storeURL = docURL.appendingPathComponent("YOUR_DATABASE_NAME.sqlite")
        do {
            let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true, NSSQLitePragmasOption: ["journal_mode": "DELETE"]] as [String : Any]
            try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
        } catch {
            fatalError("Error migrating store: \(error)")
        }
    }
    
    func commitChanges() throws{
        if self.managedObjectContext.hasChanges{
            do {
                try self.managedObjectContext.save()
            } catch {
                throw CommitError.failureToSave(error: error)
            }
        }
    }
    
    func createObject(_ entityName:String) -> NSManagedObject? {
    
        let result:NSManagedObject? = NSEntityDescription.insertNewObject(forEntityName: entityName, into: self.managedObjectContext)
        return result
    }
    
    func deleteEntity(_ entity:NSManagedObject){
        self.managedObjectContext.delete(entity)
    }
    }
    

    您可以添加其他功能来搜索或创建对象。

    如果这能解决您的问题,请告诉我 ;)

    【讨论】:

    • 为方便起见,示例代码在 AppDelegate 中创建了堆栈。任何现实世界的应用都不会在这里创建。
    • 谢谢,这对我设置核心数据堆栈的替代方式也有帮助,目前我正在使用一个经过修改的类,这不是最佳实践。
    【解决方案3】:

    我觉得有必要在 Fogmeister 的回答中添加一个少数派报告:

    1. 在最新的 SDK 中,Core Data 为我们提供了NSPersistentContainer,这绝对值得研究。您并不总是需要添加第三方依赖项来管理 Core Data。我通常在第一个可见的视图控制器中创建设置,然后根据需要将该实例注入其他视图控制器。不需要单例;测试是否可以使用 persistentContainer 配置 VC 会容易得多。
    2. 使用此持久存储的viewContext 进行获取请求。这在主队列上运行,适合驱动 UI。
    3. 可以通过调用newBackgroundContext 向persistentContainer 请求后台上下文。您可以在此队列上执行任何导入。保存时,更改会自动传播到 viewContext。或者,使用容器的 performBackgroundTask() 方法,该方法采用闭包,该闭包将在为您创建的后台队列上运行。

    编辑添加

    这是通过NSPersistentContainerperformBackgroundTask 方法将数据保存在后台队列中的一个非常基本的示例。可以从 Github 下载:https://github.com/Abizern/so-41984004

    【讨论】:

    • 哦,好的。我也会对此进行更深入的研究:) 这只是 iOS 10 吗?
    • 请注意 NSPersistentContainer 仅适用于 iOS 10.0+ 。如果您想支持 iOS V
    • 考虑到我有生产中的应用程序可以执行此操作并且可以正常工作,我不同意。
    • @Tobi 我创建了一个示例,显示更改正在传播到容器的 viewContext。 bitbucket.org/abizern/so-41984004
    • 首先非常感谢您的努力,非常感谢!好吧,我可能有点迂腐,但你通过启用默认关闭的东西来让它工作,这就是神奇发生的地方:viewContext.automaticallyMergesChangesFromParent = true我对你的反对票不满意,因为我仍然 - 现在甚至更多 -确信我的陈述是正确的,但最重要的是:矛盾得到澄清 ;-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    相关资源
    最近更新 更多