【问题标题】:Backup core data locally, and restore from backup - Swift在本地备份核心数据,并从备份中恢复 - Swift
【发布时间】:2018-07-14 18:14:50
【问题描述】:

我正在努力寻找有关创建核心数据备份的任何信息。我的最终目标是允许用户创建多个备份,并从选定的备份中恢复。

我找到了一个示例项目,它允许您在 Objective-C 中本地或通过 iCloud 进行备份/恢复,但在 swift 中没有。

有人可以帮忙吗?或者指出我正确的方向。我什至不知道从哪里开始。

【问题讨论】:

    标签: ios swift core-data backup restore


    【解决方案1】:

    我从来不需要这样做,但如果我这样做了,我就会这样做。

    进行备份

    在任何时候,使用以下步骤:

    1. 创建一个新的第二个核心数据堆栈。使用NSPersistentContainer 或旧的(但仍受支持)方法来创建NSPersistentStoreCoordinator
    2. 使用NSPersistentStoreCoordinator的函数migratePersistentStore(_:to:options:withType:)创建备份。使用 UUID 或时间戳使目标 URL 包含唯一的内容。将备份放在文档文件夹中。
    3. 按日期保留备份列表。您可以将其放入 UserDefaults 或创建一个新的属性列表文件以保存备份信息。

    步骤 #2 将从 Core Data 堆栈中删除原始存储 - 这就是您在步骤 #1 中创建第二个堆栈的原因。这样,您可以使用第二个堆栈进行备份,而不会影响您的应用正在使用的堆栈。

    如果您使用的是NSPersistentContainer,请使用其persistentStoreCoordinator 属性来执行步骤#2。

    从备份中恢复

    这有点棘手,因为您的应用可能正在使用其持久存储,但现在您想用旧版本替换它。 在从备份恢复之前,请确保您当前没有使用持久存储中的任何托管对象。 取消分配您的 NSPersistentContainer。卸载任何使用托管对象的 UI。让您的应用进入一种状态,它所能做的就是从备份中恢复或返回使用当前数据,但除了备份列表之外不显示任何数据。

    既然你已经这样做了,

    1. 显示备份列表并让用户选择一个。
    2. 使用您的数据模型创建一个NSPersistentStoreCoordinator
    3. 使用replacePersistentStore(at:destinationOptions:withPersistentStoreFrom:sourceOptions:ofType:)方法将备份数据复制到正常的应用位置。起始位置是备份位置,目标位置是应用通常保存其数据的位置。
    4. (可选)使用NSPersistentStoreCoordinator的函数destroyPersistentStore(at:ofType:options:)删除备份。
    5. 照常加载NSPersistentContainer 并重新加载常规应用 UI。

    不要使用直接与文件相关的 API,例如 FileManager。 Core Data 方法将涵盖所有与 Core Data 相关的文件,并做其他好事,例如避免导致数据损坏和遵守文件锁定。

    更新:我后来写了一篇博文,更详细地介绍了这一点,并附有示例代码:https://atomicbird.com/blog/core-data-back-up-store/

    【讨论】:

    • 我想知道...有没有人尝试过使用“允许外部存储”选项存储的大数据属性?
    • 它应该可以正常工作,与任何其他数据模型一样。
    • Ole Begemann 在此处基于此解决方案编写了一个实现:oleb.net/blog/2018/03/core-data-sqlite-backup,并澄清它确实适用于“允许外部存储”选项。
    【解决方案2】:

    详情

    • Swift 5.1,Xcode 11.3.1

    核心数据备份

     func backup(backupName: String){
            let backUpFolderUrl = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first!
            let backupUrl = backUpFolderUrl.appendingPathComponent(backupName + ".sqlite")
            let container = NSPersistentContainer(name: "Your Project Name")
            container.loadPersistentStores(completionHandler: { (storeDescription, error) in })
    
            let store:NSPersistentStore
            store = container.persistentStoreCoordinator.persistentStores.last!
            do {
                try container.persistentStoreCoordinator.migratePersistentStore(store,to: backupUrl,options: nil,withType: NSSQLiteStoreType)
            } catch {
                print("Failed to migrate")
            }
        }
    

    就是这样!

    现在,

    核心数据恢复

    func restoreFromStore(backupName: String){
    
            print(DatabaseHelper.shareInstance.getAllUsers())
            let storeFolderUrl = FileManager.default.urls(for: .applicationSupportDirectory, in:.userDomainMask).first!
            let storeUrl = storeFolderUrl.appendingPathComponent("YourProjectName.sqlite")
            let backUpFolderUrl = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first!
            let backupUrl = backUpFolderUrl.appendingPathComponent(backupName + ".sqlite")
    
            let container = NSPersistentContainer(name: "YourProjectName")
            container.loadPersistentStores(completionHandler: { (storeDescription, error) in
                let stores = container.persistentStoreCoordinator.persistentStores
    
                for store in stores {
                    print(store)
                    print(container)
                }
                do{
                    try container.persistentStoreCoordinator.replacePersistentStore(at: storeUrl,destinationOptions: nil,withPersistentStoreFrom: backupUrl,sourceOptions: nil,ofType: NSSQLiteStoreType)
                    print(DatabaseHelper.shareInstance.getAllUsers())
                } catch {
                    print("Failed to restore")
                }
    
            })
    
        }
    

    用法

    self.backup(backupName: "first_backup")
    self.restoreFromStore(backupName: "first_backup")
    

    就是这样..希望这有帮助。谢谢你

    【讨论】:

    • 您能否在您的代码中添加一些 cmets 并解释重要步骤的目的?谢谢。
    【解决方案3】:

    我在 Tom 的回答和 Apple 示例 code 的帮助下创建了以下方法。 这将备份核心数据文件并将其放置到您想要的路径。

    斯威夫特 5

        /// Backing up store type to a new and unique location
        /// The method is illustrated in the following code fragment, which shows how you can use migratePersistentStore to take a back up of a store and save it from one location to another.
        /// If the old store type is XML, the example also converts the store to SQLite.
        /// - Parameters:
        ///   - path: Where you want the backup to be done, please create a new unique directory with timestamp or the guid
        ///   - completion: Passes error in case of error or pass nil in case of success
         class func backUpCoreDataFiles(path : URL, completion : @escaping (_ error : String?) -> ())
            {
    
                // Every time new container is a must as migratePersistentStore method will loose the reference to the container on migration
                let container = NSPersistentContainer(name : "<YourDataModelName>")
                container.loadPersistentStores
                    { (storeDescription, error) in
                        if let error = error
                        {
                            fatalError("Failed to load store: \(error)")
                        }
                }
                let coordinator = container.persistentStoreCoordinator
                let store = coordinator.persistentStores[0]
                do
                {
                    try coordinator.migratePersistentStore(store, to : path, options : nil, withType : NSSQLiteStoreType)
                    completion(nil)
                }
                catch
                {
                    completion("\(Errors.coredataBackupError)\(error.localizedDescription)")
                }
            }
    

    【讨论】:

    • 我需要拍摄 coredata 堆栈的快照,对其进行压缩、密码保护并作为 blob 上传到 azure,此 blob 与时间戳一起保存,用户可以回溯到该快照数据堆栈的任何时间。
    • 路径应该是什么?我的意思是我应该像 documentsDir+"backup.sqlite" 一样通过吗??
    • 是的,路径应该添加 .sqlite
    • 嗨,在 catch 块中没有发现错误?
    • 面临此错误无法加载命名模型?请帮忙谢谢
    【解决方案4】:

    我的两分钱更有价值的想法:为了解决这个问题,我会尝试使用 CloudKit 创建不同的客户区域,以将来自核心数据的应用用户私有数据库中的不同备份存储在 iCloud 中。

    【讨论】:

    • 感谢您的建议。对我的应用来说,这个过程完全离线并且不依赖于 CloudKit 或 iCloud 很重要。所以我真的需要在本地存储。我相信这只是复制sql lite文件并保存到位置的情况。恢复。复制回来。但我不知道该怎么做。
    • 我没有离线备份的经验,但会研究将数据编码到本地文件并压缩文件以间接满足您的目的并使用 Swift。要直接备份和恢复 SQL Lite 文件,我会查看标准库,但怀疑 Swift 实现的任何发现。如果你发现了什么,请与我分享。
    猜你喜欢
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 2018-02-21
    • 1970-01-01
    • 2014-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多