应用重新启动时数据不会在 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 searchResults
在viewDidLoad
中没有效果,我怀疑代码甚至可以编译。不相关,但为什么要将提取返回的特定类型 [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 将冻结。如果您有一个超级简单的应用程序(一个小任务列表?),这将可以正常工作,但我不会推荐用于严肃应用程序的东西。对于初学者来说,学习核心数据的测试应用程序是可以的。
正确的方式 - 正确的方式是永远不要写信给viewContext
EVER。 Apple 在NSPersistentContainer
文档中记录了这一点(但也在其模板中为 viewContext 创建了一个保存方法?!)。在此设置中,所有对核心数据的写入都必须通过performBackgroundTask
,并且您必须在块结束之前在该上下文中调用save
。您还需要一个操作队列来确保没有合并冲突,请参阅NSPersistentContainer concurrency for saving to core data。这种设置很难正确完成。来自performBackgroundTask
上下文的对象不能离开块,来自viewContext
的对象不能在块中使用。编译器不会帮助您解决此问题,因此您始终需要注意这一点。
您的问题是mergePolicy
。 mergePolicy
是邪恶的。如果您在核心数据中存在冲突,则您已经做错了,任何合并策略都会丢失数据。在简单的设置中没有冲突,因为它都在一个线程上。在正确的设置中,不会因为您在使用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 中持久化的主要内容,如果未能解决你的问题,请参考以下文章
当应用程序从 4.0 中的任务列表中删除时,Core Data 存储消失
Core Data iCloud 获取有时不会产生数据(iOS)