CoreData & CloudKit 切换 iCloud 同步(启用/禁用)

huangapple go评论46阅读模式
英文:

CoreData & CloudKit toggle iCloud sync (enable/disable)

问题

自从我上次提出的问题(在此处 https://stackoverflow.com/questions/75416983/toggle-between-local-and-icloud-coredata-store/75420413#75420413),我已经取得了很多进展。

我正在在NSPersistentCloudKitContainerNSPersistenttContainer之间切换。

但是...

当我关闭CloudKit同步并更新容器时,同步仍然处于活动状态。
手动重新启动应用后,同步才会停用。

这与一些人在这里的评论中描述的问题相同...
https://stackoverflow.com/questions/65355720/coredatacloudkit-on-off-icloud-sync-toggle

但我找不到解决方案。

MyApp.swift

@main
struct MyApp: App {
    @StateObject private var persistenceContainer = PersistenceController.shared

    var body: some Scene {        
        WindowGroup {
            ContentView()
                .environmentObject(CoreBluetoothViewModel())
                .environment(\.managedObjectContext, persistenceContainer.container.viewContext)
        }
    }
}

PersistenceController

import CoreData

class PersistenceController: ObservableObject{
    
    static let shared = PersistenceController()
    
    lazy var container: NSPersistentContainer = {
        setupContainer()
    }()
    
    init() {
        container = setupContainer()
    }
    
    func updateContainer() {
        saveContext()
        container = setupContainer()
        saveContext()
    }
    
    private func setupContainer() -> NSPersistentContainer {
        let iCloud = UserDefaults.standard.bool(forKey: "iCloud")
        
        do {
            let newContainer = try PersistentContainer.getContainer(iCloud: iCloud)
            guard let description = newContainer.persistentStoreDescriptions.first else { fatalError("No description found") }
            
            if iCloud {
                newContainer.viewContext.automaticallyMergesChangesFromParent = true
                newContainer.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
            } else {
                description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
                description.cloudKitContainerOptions = nil
            }
            
            description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
            
            newContainer.loadPersistentStores { (storeDescription, error) in
                if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") }
            }
            
            return newContainer
            
        } catch {
            print(error)
        }
        
        fatalError("Could not setup Container")
    }
    
    private func saveContext() {
        do {
            try container.viewContext.save()
        } catch {
            let error = error as NSError
            fatalError("ERROR: \(error)")
        }
    }
}

final class PersistentContainer {
    
    private static var _model: NSManagedObjectModel?
    
    private static func model(name: String) throws -> NSManagedObjectModel {
        if _model == nil {
            _model = try loadModel(name: name, bundle: Bundle.main)
        }
        return _model!
    }
    
    private static func loadModel(name: String, bundle: Bundle) throws -> NSManagedObjectModel {
        guard let modelURL = bundle.url(forResource: name, withExtension: "momd") else {
            throw CoreDataModelError.modelURLNotFound(forResourceName: name)
        }

        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            throw CoreDataModelError.modelLoadingFailed(forURL: modelURL)
       }
        return model
    }

    enum CoreDataModelError: Error {
        case modelURLNotFound(forResourceName: String)
        case modelLoadingFailed(forURL: URL)
    }

    public static func getContainer(iCloud: Bool) throws -> NSPersistentContainer {
        let name = "LogModel"
        if iCloud {
            return NSPersistentCloudKitContainer(name: name, managedObjectModel: try model(name: name))
        } else {
            return NSPersistentContainer(name: name, managedObjectModel: try model(name: name))
        }
    }
}

有人有什么办法来解决这个问题吗?

英文:

Since my last question (here https://stackoverflow.com/questions/75416983/toggle-between-local-and-icloud-coredata-store/75420413#75420413) I was able to make a lot of progress.

I am switching between NSPersistentCloudKitContainer and NSPersistenttContainer

But...

When I switch off the CloudKit synchronization and update the container, the sync is still active.<br>After restarting the app manually the sync is deactivated.

This is the same problem some people are describing in the comments here...<br>
https://stackoverflow.com/questions/65355720/coredatacloudkit-on-off-icloud-sync-toggle

But I wasn't able to find a solution.

MyApp.swift

@main
struct MyApp: App {
    @StateObject private var persistenceContainer = PersistenceController.shared

    var body: some Scene {        
        WindowGroup {
            ContentView()
                .environmentObject(CoreBluetoothViewModel())
                .environment(\.managedObjectContext, persistenceContainer.container.viewContext)
        }
    }
}

PersistenceController

import CoreData

class PersistenceController: ObservableObject{
    
    static let shared = PersistenceController()
    
    lazy var container: NSPersistentContainer = {
        setupContainer()
    }()
    
    init() {
        container = setupContainer()
    }
    
    func updateContainer() {
        saveContext()
        container = setupContainer()
        saveContext()
    }
    
    private func setupContainer() -&gt; NSPersistentContainer {
        let iCloud = UserDefaults.standard.bool(forKey: &quot;iCloud&quot;)
        
        do {
            let newContainer = try PersistentContainer.getContainer(iCloud: iCloud)
            guard let description = newContainer.persistentStoreDescriptions.first else { fatalError(&quot;No description found&quot;) }
            
            if iCloud {
                newContainer.viewContext.automaticallyMergesChangesFromParent = true
                newContainer.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
            } else {
                description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
                description.cloudKitContainerOptions = nil
            }
            
            description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
            
            newContainer.loadPersistentStores { (storeDescription, error) in
                if let error = error as NSError? { fatalError(&quot;Unresolved error \(error), \(error.userInfo)&quot;) }
            }
            
            return newContainer
            
        } catch {
            print(error)
        }
        
        fatalError(&quot;Could not setup Container&quot;)
    }
    
    private func saveContext() {
        do {
            try container.viewContext.save()
        } catch {
            let error = error as NSError
            fatalError(&quot;ERROR: \(error)&quot;)
        }
    }
}

final class PersistentContainer {
    
    private static var _model: NSManagedObjectModel?
    
    private static func model(name: String) throws -&gt; NSManagedObjectModel {
        if _model == nil {
            _model = try loadModel(name: name, bundle: Bundle.main)
        }
        return _model!
    }
    
    private static func loadModel(name: String, bundle: Bundle) throws -&gt; NSManagedObjectModel {
        guard let modelURL = bundle.url(forResource: name, withExtension: &quot;momd&quot;) else {
            throw CoreDataModelError.modelURLNotFound(forResourceName: name)
        }

        guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
            throw CoreDataModelError.modelLoadingFailed(forURL: modelURL)
       }
        return model
    }

    enum CoreDataModelError: Error {
        case modelURLNotFound(forResourceName: String)
        case modelLoadingFailed(forURL: URL)
    }

    public static func getContainer(iCloud: Bool) throws -&gt; NSPersistentContainer {
        let name = &quot;LogModel&quot;
        if iCloud {
            return NSPersistentCloudKitContainer(name: name, managedObjectModel: try model(name: name))
        } else {
            return NSPersistentContainer(name: name, managedObjectModel: try model(name: name))
        }
    }
}

Does anyone have any idea how I could solve this problem?

答案1

得分: 1

在SwiftUI中,我一直比较新,但一直在努力找到解决这个问题的方法。从看起来的情况来看,我一直在关注像您一样的Stack Overflow问题和答案。

为了背景信息,我在一个iPad和一个iPhone之间的结果非常不一致,版本与我的代码非常接近,有时它可以工作,我可以切换云端Kit的开关。

然而...

在与代码玩耍、注释和阅读一些内容后,这行代码引起了我的疑问。

description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

毫无疑问,答案就在它的名字里。RemoteChangeNotificationPost。

我所做的是将这行代码与以下代码放在一起:

if iCloud {
newContainer.viewContext.automaticallyMergesChangesFromParent = true
newContainer.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}

这样,只有当启用iCloud时,它才会“发布更改”。

现在,当我关闭CloudKit时,不会发送任何更新。

当你重新打开它时,需要在设备上触发更改才能触发同步,但我觉得这是更接近结果的一步?

如果我做错了什么,请告诉我,不想采取不良做法,但到目前为止... 直到找到更好的解决方案,它都有效。


来自苹果的消息:

如果您需要在设备上存储一些数据并提供选项供用户选择CloudKit同步,请考虑创建一个本地存储和一个CloudKit存储,分别管理数据。当用户选择加入时,将数据从本地存储移动到CloudKit存储,然后让`NSPersistentCloudKitContainer`来处理其余的事情。通过使用本地存储,您可以控制何时将数据移动到CloudKit存储以及何时移动数据。

有关使用一个Core Data堆栈管理多个存储的详细信息,请参见链接数据在两个Core Data存储之间的方法:<https://developer.apple.com/documentation/coredata/linking_data_between_two_core_data_stores>
英文:

fairly new to SwiftUi still but have been struggling with finding a solution to this problem as well. By the looks of it I’ve been following all of the stackoverflow questions and answers as yourself.

For context I get a very inconsistent result between a iPad and a iPhone with a version of my code which is pretty close to mine, where sometimes it works and I can toggle on and off cloudkit.

However...

After playing around with the code and commenting on and off lines and reading some more, this line of code stood out as a question for me.

description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

surely the answer is in its name. RemoteChangeNotificationPost.

what I have done its put that line in with the

if iCloud {
newContainer.viewContext.automaticallyMergesChangesFromParent = true
newContainer.viewContext.mergePolicy =   NSMergeByPropertyStoreTrumpMergePolicy
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}

so it only "Posts Changes" when iCloud is enabled.

now when I toggle off cloudkit. no updates are sent through.

When you toggle it back on, it does require a change to happen on the device to trigger a sync it seams but it I feel its a step closer to a result?

hit me down everyone if im doing anything wrong wouldn't want to be pushing a bad practice but so far.... it works till I find a better solution.


message from Apple:

If you need to store some data on device and provide an option for users to opt in the CloudKit synchronization, consider creating a local store and a CloudKit store to manage the data separately. When users choose to opt in, you move the data from local store to the CloudKit one, and let `NSPersistentCloudKitContainer` take care the rest. By using a local  store, you control what data and when to be moved to the CloudKit store.
For details about managing multiple stores with one Core Data stack, see Linking Data Between Two Core Data Stores:
&lt;https://developer.apple.com/documentation/coredata/linking_data_between_two_core_data_stores&gt;

huangapple
  • 本文由 发表于 2023年2月14日 07:47:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75442220.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定