【问题标题】:Isolated managed object context for FetchRequest in SwiftUISwiftUI 中 FetchRequest 的独立托管对象上下文
【发布时间】:2021-04-18 19:14:38
【问题描述】:

在 SwiftUI 视图中,默认情况下 FetchRequestmanagedObjectContext 环境值中获取。相反,如果视图想要获取到isolated context,例如在不污染其他视图的上下文的情况下进行可丢弃的编辑,它如何更改FetchRequest 使用的上下文?

一种选择是将视图包装在创建隔离上下文的外部视图中,然后使用它调用包装的视图:

var body: some View {
    WrappedView().environment(\.managedObjectContext, isolatedContext)
}

然而,这很乏味。您必须创建两个视图并通过包装器传递所有包装视图的参数。有没有更好的方法来告诉FetchRequest 使用哪个上下文?

【问题讨论】:

    标签: core-data swiftui transaction-isolation


    【解决方案1】:

    如果您使用 Apple 作为初创公司提供的标准 PersistentController,您可以尝试使用

    .environment(\.managedObjectContext, privateContext)
    

    您的View 需要此属性才能使其工作。 @State 不应该是必要的,因为更改是在后台通过通知等其他方式完成的。

    let privateContext = PersistenceController.shared.container.newBackgroundContext()
    

    调用 newBackgroundContext() 方法会导致持久化容器创建并返回一个新的 NSManagedObjectContext,concurrencyType 设置为 NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType。这个新上下文将直接与 NSPersistentStoreCoordinator 关联,并设置为自动使用 NSManagedObjectContextDidSave 广播。

    然后使用 Apple 提供的大部分示例代码对其进行测试。

    struct SampleSharedCloudKitApp: App {
        let privateContext = PersistenceController.shared.container.newBackgroundContext()
        var body: some Scene {
            WindowGroup {
                VStack{
                    Text(privateContext.description) //Added this to match with ContentView
                    ContentView()
                        .environment(\.managedObjectContext, privateContext)
                        //Once you pass the privateContext here everything below it will have the privateContext
                        //You don't need to connect it with @FetchRequest by any other means
                }
            }
        }
    }
    
    struct ContentView: View {
        @Environment(\.managedObjectContext) private var viewContext
        @FetchRequest(
            sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
            animation: .default)
        private var items: FetchedResults<Item>
    
        var body: some View {
            List {
                Text((items.first!.managedObjectContext!.concurrencyType == NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType).description) //This shows true
                Text(items.first!.managedObjectContext!.description)// This description matches the parent view
                Text(viewContext.description)// This description matches the parent view
    

    另外,需要注意的是你必须设置

    container.viewContext.automaticallyMergesChangesFromParent = true
    container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
    

    为了让主上下文显示保存privateContext 后所做的更改。我把它放在 PersistenceController init loadPersistentStores 闭包之后。

    【讨论】:

    • let persistenceController 是否比直接将managedObjectContext 设置为PersistenceController.shared.container.newBackgroundContext() 有优势?
    • 我没有尝试过,但我认为拥有该属性可以防止它在每次 SwiftUI 重新加载正文时生成新的上下文。您最终可能会遇到几种不同的上下文。
    • 这个目标是有道理的,但仅仅设置persistenceController 并不能实现它。您需要将本地上下文存储为状态:@State private var localContext = PersistenceController.shared.container.newBackgroundContext()。但是,将环境应用于 FetchRequest 仍然存在问题,除非您强制调用者设置它或创建包装器。
    • 谢谢。该代码突出了激发我提出问题的问题。如果需要私有上下文是 ContentView 的实现细节,最好不要让该细节溢出到包含视图(您的示例中的应用程序)中。此外,包含视图可能没有正确的生命周期。例如,如果 ContentView 是您可以导航到的屏幕,则您希望每次导航都有一个新的上下文,而不是单例。带有@State 变量的包装视图将提供正确的生命周期,但编写代码很乏味。
    • 另一种选择是手动设置@FetchRequest,动态谓词有ton of SO questions,但如果添加fetchRequest.includesPendingChanges = false,您的结果在保存之前不应包含更改。我不确定它是否有效。我倾向于使用“老派”NSFetchedRequestController 我不喜欢包装器的局限性,而 SwiftUI 视图最终会产生如此多的持久代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多