在Swift 5中配置核心数据持久性

ang*_*yip 7 core-data ios swift

我正在使用以下教程将Core Data实现到我的Swift IOS应用程序中。如视频所示,我的持久性管理器是通过单例模式创建的。这是描述它的代码:

import Foundation
import CoreData

class DataLogger {

    private init() {}
    static let shared = DataLogger()
    lazy var context = persistentContainer.viewContext

    private var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "mycoolcontainer")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                print("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    func save () {
        if self.context.hasChanges {
            self.context.perform {
                do {
                    try self.context.save()
                } catch {
                    print("Failure to save context: \(error)")
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果使用以下代码创建一个包含约1000个左右实体元素的循环(MyEntity是Core Data实体对象),则应用程序将崩溃。

class MySampleClass {

    static func doSomething {
        for i in 0 ..< 1000 {
            let entry = MyEntity(context: DataLogger.shared.context);
            // do random things
        }
        DataLogger.shared.save()
    }
}
Run Code Online (Sandbox Code Playgroud)

它在MyEntity(context:DataLogger.shared.context)上崩溃,并且我无法观察任何日志以了解原因。有时,它将到达save()调用并成功或崩溃,并显示以下一般错误:

检测到堆损坏,空闲列表在0x280a28240处被损坏***保护值不正确:13859718129998653044

我试图在网上四处寻找问题的根源。我试图通过.performAndWait()使DataLogger中的save()方法同步,但没有成功。我还尝试通过以下代码使用childContexts执行相同的操作,但未成功:

let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
childContext.parent = context
childContext.hasChanged {
    childContext.save();
}
Run Code Online (Sandbox Code Playgroud)

我怀疑我错误地实现了DataLogger类,但无法观察到实际的问题。它可能与创建对象的数量或线程有关,但我不确定。实现DataLogger类以确保任何其他类都可以使用它并将实体存储到磁盘的正确方法是什么?

ang*_*yip 7

我最终阅读了有关 Core Data 的更多信息,并以这种方式解决了问题:

首先,我最终将持久性容器移动到应用程序委托中。它可以定义如下:

 import UIKit
 import CoreData

 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate { 

      ... applicationWillTerminate ....

      // add the core data persistance controller
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "mycoolcontainer")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                print("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container;
    }()

    func save() {
        let context = persistentContainer.viewContext;
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                print("Failure to save context: \(error)")
            }
        }
    }

 }
Run Code Online (Sandbox Code Playgroud)

在我需要访问上下文的每个视图控制器中,我会执行以下操作:

import UIKit

class CoolViewController: UIPageViewController, UIPageViewControllerDelegate {

    let appDelegate = UIApplication.shared.delegate as! AppDelegate;

    func storeAndFetchRecords() {
        MySampleClass().doSomething(context: appDelegate.persistentContainer.viewContext);
        appDelegate.save()
    }

}
Run Code Online (Sandbox Code Playgroud)

需要与上下文交互的类将按如下方式进行:

import CoreData

class MySampleClass {

    func doSomething(context: NSManagedObjectContext) {
        for i in 0 ..< 1000 {
            let entry = MyEntity(context: context);
            // do random things
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

由于它存在于主线程上,因此无需运行“performAndWait”。这仅在您通过以下语法启动后台上下文时才有意义:

appDelegate.persistentContainer.newBackgroundContext()
Run Code Online (Sandbox Code Playgroud)


and*_*der 5

我决定添加第二个答案,因为该方法与我之前的答案不同。

对您的问题的进一步阅读表明内存分配存在问题。

简而言之,我的评价是...

  1. 您的迭代for i in 0 ..< 1000在形式上是一个同步过程,但由于内部对 Core Data 的调用,可能与 Core Data 过程“不同步”。我怀疑for...in迭代中正在进行的任何核心数据进程在您的下一次迭代开始时物理上无法完成,并且您的代码正遭受非法内存覆盖。

或者

  1. 对于 1000 个实体,您的调用对性能.save造成了巨大的“打击”,并且根据您当时正在运行的其他进程,尤其是修改 . 的进程NSManagedObjectContext,您可能会耗尽系统资源。我怀疑,根据您在调用时在代码中运行的“其他”进程.save,您只是缺乏系统资源,内存覆盖可能是一种机制,尽管失败了,作为 iOS 的一种尝试“保持向上”。

是的,我在这里涵盖了很多领域,但试图为您指出一个有助于解决问题的方向。

也许您的问题变成了……在步进和/或保存之前,我如何确保迭代中的核心数据流程是完整的?

我在 Core Data 上读过的所有书籍和其他信息都表明,在一个内部的操作NSManagedObjectContext是便宜的,而保存是昂贵的。

以下是一本书的直接引用,如果我相信作者的话,我怀疑在本网站上是允许的...

“但重要的是要注意,将新对象插入上下文或更改现有对象都非常便宜——这些操作只涉及托管对象上下文。插入和更改都在上下文中操作,而不会触及 SQLite 层。

只有在将这些更改保存到商店时,成本才相对较高。保存操作涉及协调器层和 SQL 层。因此,对 save() 的调用相当昂贵。

关键要点很简单,就是减少保存次数。但是有一个平衡点:保存大量更改会增加内存使用量,因为上下文必须跟踪这些更改,直到它们被保存。处理较大的更改需要比较小的更改更多的 CPU 和内存。但是,总的来说,大型变更集比许多小型变更集便宜。”

摘自:“核心数据”。作者:弗洛里安·库格勒和丹尼尔·埃格特。(对象.io )

我现在没有给你一个明确的答案,因为坦率地说,这需要我花费一些时间和精力来解决,但首先我建议你:

  • 阅读性能调优核心数据;
  • 调查剥离MyEntity(context:)对基本要素的调用- 例如,创建一个有relationship错误的实体或创建一个实体并仅attributes将代码运行所需的那些调用到内存中;
  • 调查使用NSOperationOperation来排队您的任务;
  • 观看 WWDC2015 关于“高级 NSOperations”的演讲

进一步阅读,如果您有兴趣: