本地核心数据备份/恢复iCloud冲突“被告知拆除原因:存储已更改”

问题描述 投票:0回答:1

我有一个核心数据的备份/恢复功能,定义如下:

import CoreData
import FirebaseCrashlytics
import Foundation

extension NSPersistentCloudKitContainer {
    enum CopyPersistentStoreErrors: Error {
        case invalidDestination(String)
        case destinationError(String)
        case destinationNotRemoved(String)
        case copyStoreError(String)
        case invalidSource(String)
    }
    
    func restorePersistentStore(from backupURL: URL) throws {
        guard backupURL.isFileURL else {
            throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL")
        }
        
        var isDirectory: ObjCBool = false
        if FileManager.default.fileExists(atPath: backupURL.path, isDirectory: &isDirectory) {
            if !isDirectory.boolValue {
                throw CopyPersistentStoreErrors.invalidSource("Source URL must be a directory")
            }
        } else {
            throw CopyPersistentStoreErrors.invalidSource("Source URL must exist")
        }
        
        for persistentStoreDescription in persistentStoreDescriptions {
            guard let loadedStoreURL = persistentStoreDescription.url else {
                continue
            }
            let backupStoreURL = backupURL.appendingPathComponent(loadedStoreURL.lastPathComponent)
            guard FileManager.default.fileExists(atPath: backupStoreURL.path) else {
                throw CopyPersistentStoreErrors.invalidSource("Missing backup store for \(backupStoreURL)")
            }
            do {
                var storeOptions = persistentStoreDescription.options
                storeOptions.updateValue(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
                let configurationName = persistentStoreDescription.configuration
                let storeType = persistentStoreDescription.type
                
                // Replace the current store with the backup copy. This has a side effect of removing the current store from the Core Data stack.
                // When restoring, it's necessary to use the current persistent store coordinator.
                try persistentStoreCoordinator.replacePersistentStore(at: loadedStoreURL, destinationOptions: storeOptions, withPersistentStoreFrom: backupStoreURL, sourceOptions: storeOptions, ofType: storeType)
                // Add the persistent store at the same location we've been using, because it was removed in the previous step.
                try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: configurationName, at: loadedStoreURL, options: storeOptions)
            } catch {
                Crashlytics.crashlytics().record(error: error)
                throw CopyPersistentStoreErrors.copyStoreError("Could not restore: \(error.localizedDescription)")
            }
        }
    }
    
 
    func copyPersistentStores(to destinationURL: URL, overwriting: Bool = false) throws {
        guard destinationURL.isFileURL else {
            throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a file URL")
        }
        
        var isDirectory: ObjCBool = false
        if !overwriting, FileManager.default.fileExists(atPath: destinationURL.path, isDirectory: &isDirectory) {
            if !isDirectory.boolValue {
                throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a directory")
            }
 
        }

        if overwriting, FileManager.default.fileExists(atPath: destinationURL.path) {
            do {
                try FileManager.default.removeItem(at: destinationURL)
            } catch {
                Crashlytics.crashlytics().record(error: error)
                throw CopyPersistentStoreErrors.destinationNotRemoved("Can't overwrite destination at \(destinationURL)")
            }
        }
        
        // Create the destination directory
        do {
            try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
        } catch {
            Crashlytics.crashlytics().record(error: error)
            throw CopyPersistentStoreErrors.destinationError("Could not create destination directory at \(destinationURL)")
        }
        
        for persistentStoreDescription in persistentStoreDescriptions {
            guard let storeURL = persistentStoreDescription.url else {
                continue
            }
            guard persistentStoreDescription.type != NSInMemoryStoreType else {
                continue
            }
            let destinationStoreURL = destinationURL.appendingPathComponent(storeURL.lastPathComponent)
            
            if !overwriting, FileManager.default.fileExists(atPath: destinationStoreURL.path) {
                // If the destination exists, the replacePersistentStore call will update it in place. That's fine unless we're not overwriting.
                throw CopyPersistentStoreErrors.destinationError("Destination already exists at \(destinationStoreURL)")
            }
            do {
                // Replace an existing backup, if any, with a new one with the same options and type. This doesn't affect the current Core Data stack.
                // The function name says "replace", but it works if there's nothing at the destination yet. In that case it creates a new persistent store.
                // Note that for backup, it doesn't matter if the persistent store coordinator is the one currently in use or a different one. It could be a class function, for this use.
                try persistentStoreCoordinator.replacePersistentStore(at: destinationStoreURL, destinationOptions: persistentStoreDescription.options, withPersistentStoreFrom: storeURL, sourceOptions: persistentStoreDescription.options, type: .sqlite)
            } catch {
                Crashlytics.crashlytics().record(error: error)
                throw CopyPersistentStoreErrors.copyStoreError("\(error.localizedDescription)")
            }
        }
    }
}

备份和恢复功能在本地按预期工作。但是,当与 CloudKit 一起使用时,我在控制台中收到以下错误,并且所有数据在重新启动后都会丢失,并被 iCloud 中存储的数据覆盖:

CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate tearDown:]_block_invoke(808): <NSCloudKitMirroringDelegate: 0x302f9c960>: Told to tear down with reason: Stores Changed

随后:


error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate recoverFromError:](2302): <NSCloudKitMirroringDelegate: 0x302f9c960> - Attempting recovery from error: Error Domain=NSCocoaErrorDomain Code=134404 "(null)" UserInfo={NSDetailedErrors=(
    "Error Domain=NSCocoaErrorDomain Code=134407 \"Request 'ECA8B22F-4F97-4D7E-B6B9-D61CC5F2C4D9' was cancelled because the store was removed from the coordinator.\"

CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](605): <NSCloudKitMirroringDelegate: 0x302f9c960> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134404 "(null)" UserInfo={NSDetailedErrors=(
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _scheduleAutomatedImportWithLabel:activity:voucher:completionHandler:]_block_invoke(3633): <NSCloudKitMirroringDelegate: 0x302f9c960> - Finished automatic import - SandboxImport - with result: <NSCloudKitMirroringResult: 0x301cb1650> storeIdentifier: 2A4E461C-6CBD-4EB1-8C3B-C8487E191315 success: 0 madeChanges: 1 error: Error Domain=NSCocoaErrorDomain Code=134410 "(null)" UserInfo={NSUnderlyingError=0x301eef420 {Error Domain=NSCocoaErrorDomain Code=134404 "(null)" UserInfo={NSDetailedErrors=(
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate checkAndExecuteNextRequest](3535): <NSCloudKitMirroringDelegate: 0x302f9c960>: Checking for pending requests.

CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](605): <NSCloudKitMirroringDelegate: 0x302f9c960> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134404 "(null)" UserInfo={NSDetailedErrors=(
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _scheduleAutomatedImportWithLabel:activity:voucher:completionHandler:]_block_invoke(3633): <NSCloudKitMirroringDelegate: 0x302f9c960> - Finished automatic import - SandboxImport - with result: <NSCloudKitMirroringResult: 0x301cb1650> storeIdentifier: 2A4E461C-6CBD-4EB1-8C3B-C8487E191315 success: 0 madeChanges: 1 error: Error Domain=NSCocoaErrorDomain Code=134410 "(null)" UserInfo={NSUnderlyingError=0x301eef420 {Error Domain=NSCocoaErrorDomain Code=134404 "(null)" UserInfo={NSDetailedErrors=(
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate checkAndExecuteNextRequest](3535): <NSCloudKitMirroringDelegate: 0x302f9c960>: Checking for pending requests.

我想知道

restorePersistentStore
函数中缺少哪个步骤来告诉 iCloud 使用新恢复的持久存储数据?

swift core-data icloud cloudkit
1个回答
0
投票

我想分享Apple DTS的答案。

“当使用 Core Data + CloudKit 存储时,Core Data 维护 Core Data 存储和 CloudKit 数据库之间的同步状态。当您用新存储替换存储时,Core Data 会丢失状态,因此不知道如何将新存储与关联的 CloudKit 数据库同步。

要使用备份存储作为新的事实,您需要删除CloudKit中的所有数据,并重新创建整个数据集,以便将其同步到CloudKit。

或者,如果您能够设法了解当前存储和备份存储之间的更改,则可以将更改应用到当前存储。”

建议向 Core Data 团队提交反馈以考虑作为 API 请求。位于FB13816791下

© www.soinside.com 2019 - 2024. All rights reserved.