首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将现有应用程序从NSPersistentContainer迁移到NSPersistentCloudKitContainer

将现有应用程序从NSPersistentContainer迁移到NSPersistentCloudKitContainer
EN

Stack Overflow用户
提问于 2021-10-17 14:59:59
回答 2查看 185关注 0票数 2

我有一个只使用本地设备CoreData (NSPersistentContainer)的应用程序。我正在寻找迁移,以便该应用程序与NSPersistentCloudKitContainer兼容。我了解NSPersistentCloudKitContainer的所有CloudKit设置,但如何将播放器手机上的数据迁移到iCloud?(即如何将现有的核心数据从NSPersistentContainer迁移到NSPersistentCloudKitContainer)?

EN

回答 2

Stack Overflow用户

发布于 2021-10-27 11:40:58

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

要点是:

  • 使用容器函数NSPersistentContainer替换iCloud架构的ist子类(此操作只需在设置或更改核心数据模型后执行一次)。initializeCloudKitSchema
  • 在iCloud仪表板中,创建每个自定义类型(这些类型以CD_开头)并将所有经过身份验证的用户的所有CD_记录类型的安全类型设置为read/write.
  • Implement
  • iCloud CD_

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

代码语言:javascript
复制
    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
    }   ()  

编辑:

privateConfigurationNamesharedConfigurationName都是Strings

代码语言:javascript
复制
let privateConfigurationName = "Private"
let sharedConfigurationName  = "Shared"  

PrivateShared作为核心数据模型中的配置名称,例如:

必须在那里将实体分配给持久存储。

警告:

如果您将同一实体分配给多个持久性存储区,则托管上下文的保存会将其存储在所有分配的存储区中,除非您指定了特定的存储区,请参阅this post

同样,fetch将从实体分配到的所有持久存储中获取一条记录,除非在fetch请求中设置了affectedStores,请参阅the docs

票数 0
EN

Stack Overflow用户

发布于 2022-02-15 18:32:59

我做了以下工作:

添加了支持NSPersistentCloudKitContainer

  • 取代了NSPersistentContainer

代码语言:javascript
复制
container = NSPersistentCloudKitContainer(name: "myApp") // <<<<< this
if inMemory {
    container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
        
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber, 
                       forKey: NSPersistentHistoryTrackingKey) // <<<<< this

container.viewContext.automaticallyMergesChangesFromParent = true
container.loadPersistentStores(...)

编辑:嗯,我说得太快了。它不工作:(

我发现人们在这里https://developer.apple.com/forums/thread/120328有一些小技巧(例如,编辑项目以触发同步,或者像这里解释的那样手动传输每个对象https://medium.com/@dmitrydeplov/coredata-cloudkit-integration-for-a-live-app-57b6cfda84ad)

但是没有实际的答案。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69605574

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档