【问题标题】:Migrating existing app from NSPersistentContainer to NSPersistentCloudKitContainer将现有应用从 NSPersistentContainer 迁移到 NSPersistentCloudKitContainer
【发布时间】:2023-03-26 08:05:02
【问题描述】:

我有一个仅使用本地设备 CoreData (NSPersistentContainer) 的应用程序。我正在寻求迁移,因此该应用程序与 NSPersistentCloudKitContainer 兼容。我了解 NSPersistentCloudKitContainer 的所有 CloudKit 设置,但是如何将玩家手机上的数据迁移到 iCloud? (即如何将现有核心数据从 NSPersistentContainer 迁移到 NSPersistentCloudKitContainer)?

【问题讨论】:

    标签: swift xcode core-data core-data-migration nspersistentcloudkitcontainer


    【解决方案1】:

    2019 年 WWDC 视频“Using Core Data With CloudKit”中给出了如何做到这一点的很好的介绍。
    要点是:

    • 用ist子类NSPersistentClouKitContainer替换NSPersistentContainer
    • 使用容器函数 initializeCloudKitSchema 初始化 iCloud 架构(只需在设置或更改核心数据模型后执行一次)。
    • 在 iCloud 仪表板中,使每个自定义类型(这些以 CD_ 开头的类型)都可查询。
    • 在 iCloud Dashboard 中,将所有经过身份验证的用户的所有 CD_ 记录类型的安全类型设置为读/写。
    • 实现核心数据的历史跟踪 (here are Apple’s suggestions)。

    如果您有多个持久性存储(例如,仅与一台设备相关的本地存储、与具有相同 Apple ID 的所有用户共享的私有存储以及与其他用户共享的共享存储),一种设置方法如下:

        private (set) lazy var persistentContainer: NSPersistentCloudKitContainer! = {
            // This app uses 3 stores: 
            //  - A local store that is user-specific,
            //  - a private store that is synchronized with the iCloud private database, and
            //  - a shared store that is synchronized with the iCloud shared database.
            
            let persistentStoresLoadedLock = DispatchGroup.init() // Used to wait for loading the persistent stores
        
            // Configure local store
            // --------------------------------------------------------------------------------------------------
            let appDocumentsDirectory = try! FileManager.default.url(for: .documentDirectory, 
                                                                                                                             in: .userDomainMask, 
                                                                                                                             appropriateFor: nil, 
                                                                                                                             create: true)
            let coreDataLocalURL = appDocumentsDirectory.appendingPathComponent("CoreDataLocal.sqlite")
            let localStoreDescription = NSPersistentStoreDescription(url: coreDataLocalURL)
            localStoreDescription.configuration = localConfigurationName
            // --------------------------------------------------------------------------------------------------
        
            // Create a container that can load the private store as well as CloudKit-backed stores.
            let container = NSPersistentCloudKitContainer(name: appName)
            assert(container.persistentStoreDescriptions.count == 1, "###\(#function): Failed to retrieve a persistent store description.")
            let firstPersistentStoreDescription = container.persistentStoreDescriptions.first!
            let storeURL = firstPersistentStoreDescription.url!
            let storeURLwithoutLastPathComponent = storeURL.deletingLastPathComponent
            
            // Configure private store
            // --------------------------------------------------------------------------------------------------
            let privateStoreDescription = firstPersistentStoreDescription
            privateStoreDescription.configuration = privateConfigurationName
            // The options below have to be set before loadPersistentStores
            // Enable history tracking and remote notifications
            privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
            privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
            privateStoreDescription.cloudKitContainerOptions!.databaseScope = .private
            // --------------------------------------------------------------------------------------------------
        
            // Configure shared store
            // --------------------------------------------------------------------------------------------------
            let sharedStoreURL = storeURLwithoutLastPathComponent().appendingPathComponent("Shared")
            let sharedStoreDescription = NSPersistentStoreDescription(url: sharedStoreURL)
            sharedStoreDescription.configuration = sharedConfigurationName
            sharedStoreDescription.timeout                                                          = firstPersistentStoreDescription.timeout
            sharedStoreDescription.type                                                                 = firstPersistentStoreDescription.type
            sharedStoreDescription.isReadOnly                                                   = firstPersistentStoreDescription.isReadOnly
            sharedStoreDescription.shouldAddStoreAsynchronously                 = firstPersistentStoreDescription.shouldAddStoreAsynchronously
            sharedStoreDescription.shouldInferMappingModelAutomatically = firstPersistentStoreDescription.shouldInferMappingModelAutomatically
            sharedStoreDescription.shouldMigrateStoreAutomatically          = firstPersistentStoreDescription.shouldMigrateStoreAutomatically
            // The options below have to be set before loadPersistentStores
            // Enable history tracking and remote notifications
            sharedStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
            sharedStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
            sharedStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions.init(containerIdentifier: "iCloud.com.zeh4soft.shop")
            // For sharing see https://developer.apple.com/documentation/cloudkit/shared_records
            // and https://medium.com/@adammillers/cksharing-step-by-step-33800c8950d2
            sharedStoreDescription.cloudKitContainerOptions!.databaseScope = .shared
            // --------------------------------------------------------------------------------------------------
        
            container.persistentStoreDescriptions = [localStoreDescription, privateStoreDescription, sharedStoreDescription]
            for _ in 1 ... container.persistentStoreDescriptions.count { persistentStoresLoadedLock.enter() }
        
            container.loadPersistentStores(completionHandler: { (storeDescription, error) in
                // The completion handler will be called once for each persistent store that is created.
                guard error == nil else {
                    /*
                    Apple suggests to replace this implementation with code to handle the error appropriately.
                    However, there is not really an option to handle it, see <https://stackoverflow.com/a/45801384/1987726>.
                    Typical reasons for an error here include:
                    * The parent directory does not exist, cannot be created, or disallows writing.
                    * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                    * The device is out of space.
                    * The store could not be migrated to the current model version.
                    Check the error message to determine what the actual problem was.
                    */
                    fatalError("###\(#function): Failed to load persistent stores: \(error!)")
                }
                if storeDescription.configuration == self.privateConfigurationName {
                    /*
                    Only if the schema has been changed, it has to be re-initialized.
                    Due to an Apple bug, this can currently (iOS 13) only be done with a .private database!
                    A re-initialization requires to run the app once using the scheme with the "-initSchema" argument.
                    After schema init, ensure in the Dashboard:
                    For every custom type, recordID and modTime must have queryable indexes.
                    All CD record types must have read/write security type for authenticated users.
                    Run later always a scheme without the "-initSchema" argument.
                    */
                    if ProcessInfo.processInfo.arguments.contains("-initSchema") {
                        do {
                            try container.initializeCloudKitSchema(options: .printSchema)
                        } catch {
                            print("-------------------- Could not initialize cloud kit schema --------------------")
                        }
                    }
                }
                persistentStoresLoadedLock.leave() // Called for all stores
            })
            let waitResult = persistentStoresLoadedLock.wait(timeout: .now() + 100) // Wait for local, private and shared stores loaded
            if waitResult != .success { fatalError("Timeout while loading persistent stores") }
            return container
        }   ()  
    

    【讨论】:

      猜你喜欢
      • 2020-07-29
      • 1970-01-01
      • 2021-11-12
      • 1970-01-01
      • 2019-06-23
      • 2016-10-09
      • 2016-07-11
      • 1970-01-01
      • 2015-02-13
      相关资源
      最近更新 更多