应用重新启动时数据不会在 Core Data 中持久化

Posted

技术标签:

【中文标题】应用重新启动时数据不会在 Core Data 中持久化【英文标题】:Data not persistent in Core Data when app re-launches 【发布时间】:2017-06-24 09:07:17 【问题描述】:

我在 XCode 8, swift 3 中制作的项目中第一次使用 Core Data。我使用了后台上下文(调用 container.performBackgroundTask 块..)来保存数据和主上下文来获取数据。当我的应用重新启动时,我保存在私有后台上下文中的数据将被删除。

请告诉我哪里出错了!!!

这里我调用了CoreDataManager类在appDelegate类的applicationDidEnterBackground和applicationWillTerminate方法中保存上下文方法:

class AppDelegate: UIResponder, UIApplicationDelegate 
var window: UIWindow?
lazy var coreDataMgrInstance = CoreDataManager.sharedInstance

func applicationDidEnterBackground(_ application: UIApplication) 

    coreDataMgrInstance.saveContext()

func applicationWillTerminate(_ application: UIApplication) 
    coreDataMgrInstance.saveContext()

这是我用来启动 NSpersistentContainer 的 Singleton 类 CoreDataManager

class CoreDataManager: NSObject     
class var sharedInstance: CoreDataManager 
    struct Singleton 
        static let instance = CoreDataManager()
    

    return Singleton.instance

   private override init() 

    super.init()


 lazy var persistentContainer: NSPersistentContainer = 

   let container = NSPersistentContainer(name: "E_CareV2")

    let description = NSPersistentStoreDescription() // enable auto lightweight migratn
    description.shouldInferMappingModelAutomatically = true
    description.shouldMigrateStoreAutomatically = true

    container.persistentStoreDescriptions = [description]

    container.loadPersistentStores(completionHandler:  (storeDescription, error) in
        if let error = error as NSError? 
            fatalError("Unresolved error \(error), \(error.userInfo)")
        
    )
    container.viewContext.automaticallyMergesChangesFromParent = true
    return container
()
func saveContext()

    print("saveContext")
    let context = persistentContainer.viewContext
    if context.hasChanges 
        do 
            try context.save()
         catch 
            let nserror = error as NSError
            fatalError("Failure to save main context \(nserror), \(nserror.userInfo)")
        

现在这是我从 Core Data 保存和获取数据的类

class SenderViewController: UIViewController 

var persistentContainer: NSPersistentContainer!

override func viewDidLoad() 
    super.viewDidLoad()

 persistentContainer = CoreDataManager.sharedInstance.persistentContainer
 let results = self.fetchPersistentData(entityName: "School", withPredicate: nil)
    print("results \n \(results)")       
  

 @IBAction func enterPressed(_ sender: Any) 

  self.persistentContainer.performBackgroundTask( (backgroundContext) in

        backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump

        let schoolentity = NSEntityDescription.insertNewObject(forEntityName: "School", into: backgroundContext) as! School

                schoolentity.schoolName = "ABC"
                schoolentity.schoolWebSite = "http://anywebsite.com/"

            do 
                try backgroundContext.save()
             catch 
                fatalError("Failure to save background context: \(error)")
                
    )


func fetchPersistentData(entityName: String?, withPredicate: NSPredicate?) -> [NSManagedObject]

        let context = self.persistentContainer.viewContext
        let request: NSFetchRequest<School> = School.fetchRequest()
        let newentity = NSEntityDescription.entity(forEntityName: entityName!, in: context)

        request.entity = newentity
        request.returnsObjectsAsFaults = false

        do 
            let searchResults = try context.fetch(request) as [NSManagedObject]
            return searchResults               
         catch 
            print("Error with request: \(error)")
        
 return []

【问题讨论】:

return searchResultsviewDidLoad 中没有效果,我怀疑代码甚至可以编译。不相关,但为什么要将提取返回的特定类型 [School] 转换为更不特定的类型 [NSManagedObject] 我刚刚编辑了代码。我得到的结果很好。但是当我重新运行应用程序时,我没有得到相同的结果。 很可能是这样。因为您依赖应用生命周期事件(例如进入后台)来保存更改,而不是在进行更改时保存更改。 我在 SenderViewController 类中进行更改时保存了背景上下文 【参考方案1】:

实际上轻量级迁移是默认启用的,你可以在截图看到

所以你可以安全地删除这些行:

let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true

container.persistentStoreDescriptions = [description]

之后一切都应该正常了。

【讨论】:

【参考方案2】:

NSPersistentContainer有两种使用方式——简单的方式和正确的方式。您正在混合它们,这会导致问题。 Apple 在其文档中对此有些不一致,因此您感到困惑是可以理解的。

简单的方法 - 简单的方法是只使用viewContext 进行读取和写入,并且只能在主线程中使用。从来没有任何冲突,因为核心数据是通过单个线程评估的。这种方法的问题是你不能在后台线程中运行任何东西。因此,如果您要导入大量数据,UI 将冻结。如果您有一个超级简单的应用程序(一个小任务列表?),这将可以正常工作,但我不会推荐用于严肃应用程序的东西。对于初学者来说,学习核心数据的测试应用程序是可以的。

正确的方式 - 正确的方式是永远不要写信给viewContextEVER。 Apple 在NSPersistentContainer 文档中记录了这一点(但也在其模板中为 viewContext 创建了一个保存方法?!)。在此设置中,所有对核心数据的写入都必须通过performBackgroundTask,并且您必须在块结束之前在该上下文中调用save。您还需要一个操作队列来确保没有合并冲突,请参阅NSPersistentContainer concurrency for saving to core data。这种设置很难正确完成。来自performBackgroundTask 上下文的对象不能离开块,来自viewContext 的对象不能在块中使用。编译器不会帮助您解决此问题,因此您始终需要注意这一点。

您的问题是mergePolicymergePolicy 是邪恶的。如果您在核心数据中存在冲突,则您已经做错了,任何合并策略都会丢失数据。在简单的设置中没有冲突,因为它都在一个线程上。在正确的设置中,不会因为您在使用performBackgroundTask 时创建的队列而发生冲突。问题是,如果您同时使用performBackgroundTask 并编写viewContext,您可能会发生冲突并且会丢失数据。就个人而言,我认为最好没有mergePolicy 并崩溃然后默默地丢失数据。

【讨论】:

你能告诉我,我应该使用什么上下文来保存 applicationWillTerminate() 方法中的数据以及如何做到这一点?删除合并策略对我不起作用。 您不必在applicationWillTerminate 中保存任何内容。每个更改都应该发生在以保存结束的 performBackgroundTask 中。因此,您不应该在退出时担心未保存的上下文浮动。 我删除了 saveContext 和 mergepolicy,在主上下文中获取数据并保存在 performBackgroundTask 块中,但没有任何帮助。我仍然找不到我在上次运行中保存的数据。 那我怀疑你正在删除数据的应用程序中执行其他操作。 我是说我查看了您的代码,但没有发现问题。所以我怀疑还有其他代码正在删除数据库。【参考方案3】:

我找到了导致我的数据在 Core Data 中不持久的原因。我在 persistentContainer 定义中放入了以下 4 行代码,用于启用 LIGHTWEIGHT MIGRATION 模型:

let description = NSPersistentStoreDescription() // enable auto lightweight migratn
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true

container.persistentStoreDescriptions = [description]

删除这些行后,我可以在应用重新启动时保留我的数据。 我已经编写了上述代码来启用轻量级迁移到我的模型,但我没有更改我的模型或创建模型的新版本,导致核心数据无法在 NSBundle 中搜索目标模型,因此无法推断映射。

我仍然不确定,这将如何删除我的数据,但我也会继续尝试解决这个问题,并在我成功时发表评论...... :)

【讨论】:

以上是关于应用重新启动时数据不会在 Core Data 中持久化的主要内容,如果未能解决你的问题,请参考以下文章

Core Data 默认使用线程吗?

当应用程序从 4.0 中的任务列表中删除时,Core Data 存储消失

Core Data 对象有时不会保存到永久存储中

Core Data iCloud 获取有时不会产生数据(iOS)

如何通过删除和重新安装 App 将数据持久化在 Core Data 中

Core Data + CloudKit 不会在其他设备上自动刷新?