简体   繁体   English

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

[英]Data not persistent in Core Data when app re-launches

I am using Core Data for the first time in a project made in XCode 8, swift 3. I have used background context (calling container.performBackgroundTask block..) to save the data and main context to fetch the data. 我在XCode 8,swift 3中创建的项目中第一次使用Core Data。我使用后台上下文(调用container.performBackgroundTask块...)来保存数据和主上下文以获取数据。 When my app re-launches, the data I saved in the private background context is deleted. 当我的应用重新启动时,我保存在私有背景上下文中的数据将被删除。

Please tell me where I went wrong !!! 请告诉我哪里出错!!!

Here I call CoreDataManager class save context method in applicationDidEnterBackground and applicationWillTerminate methods of AppDelegate class: 这里我在AppDlegate类的applicationDidEnterBackground和applicationWillTerminate方法中调用CoreDataManager类保存上下文方法:

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

func applicationDidEnterBackground(_ application: UIApplication) {

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

Here is my Singleton class CoreDataManager to initiate NSpersistentContainer 这是我的Singleton类CoreDataManager来启动NSpersistentContainer

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)")
        }
}}

Now this is the class where I save and fetch the data from Core Data 现在,这是我保存并从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 []
}

Actually the lightweight migrations are enabled by default as you can see on the screenshot 实际上,默认情况下会启用轻量级迁移,如屏幕截图所示 在此输入图像描述

So you can safely delete these lines: 所以你可以安全地删除这些行:

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

container.persistentStoreDescriptions = [description]

After that everything should work. 之后一切都应该有效。

I figured out what causes my data not to be persistent in Core Data. 我弄清楚是什么导致我的数据不能在Core Data中持久化。 It was these below 4 lines of code that I put in persistentContainer definition for enabling LIGHTWEIGHT MIGRATION of models: 我在persistentContainer定义中添加了以下4行代码,用于启用模型的LIGHTWEIGHT MIGRATION

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

container.persistentStoreDescriptions = [description]

When I deleted these lines, I became able to retain my data when the app re-launches. 当我删除这些行时,我可以在应用重新启动时保留我的数据。 I had written the above lines to enable Lightweight Migration to my model, but I did not change my model or created new version of the model, making the Core Data unable to search the destination model in NSBundle and thus unable to infer the Mapping. 我编写了上面的行来为我的模型启用Lightweight Migration,但是我没有更改我的模型或创建模型的新版本,使得Core Data无法在NSBundle中搜索目标模型,因此无法推断Mapping。

I am still not sure, how that would delete my data but I will keep trying to figure this out too and comment when I get success in it... :) 我仍然不确定,这将如何删除我的数据,但我会继续尝试解决这个问题并在我获得成功时发表评论...... :)

There are two ways to use NSPersistentContainer - the simple way and the correct way. 有两种方法可以使用NSPersistentContainer - 简单的方法和正确的方法。 You are mixing them which is leading to problems. 你正在混合它们导致问题。 Apple is a little inconsistent on this in its documentation so it is understandable that you are confused. Apple在其文档中对此有点不一致,因此您感到困惑是可以理解的。

The Simple Way - The simple way is only use the viewContext both for reading and for writing and only from the main thread. 简单方法 - 简单的方法是仅使用viewContext进行读取和写入,并且仅使用主线程。 There are never any conflicts because core-data is assessed via a single thread. 从来没有任何冲突,因为核心数据是通过单个线程评估的。 The problem with this approach is that you can't run anything in a background thread. 这种方法的问题是你不能在后台线程中运行任何东西。 So if you are importing a lot of data the UI will freeze. 因此,如果要导入大量数据,UI将会冻结。 If you have a super simple app (a small task list?) this will work OK, but not something that I would ever recommend for a serious app. 如果你有一个超级简单的应用程序(一个小任务列表?),这将工作正常,但不是我会推荐给一个严肃的应用程序。 It is OK for a testing app for a beginner to learn core-data. 对于初学者来说,测试应用程序可以学习核心数据。

The Correct Way - the correct way is to NEVER write to the viewContext EVER. 正确的方法 - 正确的方法是永远不要写入viewContext EVER。 Apple documents this in NSPersistentContainer documentation (but also in its template creates a save method for the viewContext?!). Apple在NSPersistentContainer文档中记录了这NSPersistentContainer (但在其模板中也为viewContext创建了一个save方法?!)。 In this setup all writes to core data MUST go through performBackgroundTask and you have to call save on that context before the end of the block. 在此设置中,对核心数据的所有写入必须通过performBackgroundTask并且必须在块结束之前调用该上下文的save There are no merge conflicts because all writes go through performBackgroundTask which internally has a serial queue. 没有合并冲突,因为所有写操作都通过performBackgroundTask ,内部有一个串行队列。 This setup is a lot harder to do correctly. 这种设置很难正确完成。 Objects from performBackgroundTask contexts cannot leave the block and objects from the viewContext cannot be used in the block. 来自performBackgroundTask上下文的对象不能离开块,而viewContext对象不能在块中使用。 The complier doesn't help you with this so it is something that you always need to watch out for. 编译器对此没有帮助,所以这是你需要注意的事情。

Your problem is mergePolicy . 你的问题是mergePolicy mergePolicy is evil. mergePolicy是邪恶的。 If you have conflicts in core-data you have already done something wrong and any merge policy will lose data. 如果您在核心数据中存在冲突,那么您已经做错了,任何合并策略都会丢失数据。 In the simple setup there are no conflicts because it is all on one thread. 在简单的设置中没有冲突,因为它全部在一个线程上。 In the correct setup there is no conflicts because performBackgroundTask has a queue. 在正确的设置中没有冲突,因为performBackgroundTask有一个队列。 The problem is that if you use BOTH performBackgroundTask and write the the viewContext you can get conflicts and you will lose data. 问题是,如果你使用BOTH performBackgroundTask并编写viewContext你可能会遇到冲突,你将丢失数据。 Personally, I think it is better to have no mergePolicy and crash then to silently lose data. 就个人而言,我认为最好没有mergePolicy然后崩溃然后默默地丢失数据。

TL;TD: Remove the method saveContext , remove the mergePolicy and never write to the viewContext . TL; TD:删除saveContext方法,删除mergePolicy ,永远不要写入viewContext

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM