swift - 如何正确使用查询生成 token ?

标签 swift core-data swiftui

我正在尝试使用 CoreData 和 QueryGenerationTokens 运行一个示例项目。该项目的本质是在计时器上提交对背景上下文的更改(模拟从服务器传来的更改),在 UI 上执行操作(例如,按下按钮)之前不应显示这些更改。

目前,我在后台上下文中保存了更改(每 5 秒添加并保存一个实体)并且它们自动进入 View 上下文(正如预期的那样,.automaticallyMergesChangesFromParent 设置为 true)。 哪里出了问题,我会在当前查询生成 token 发生任何这些更改之前固定 View 上下文。我希望 View 不会随着添加的背景项目而更新,但会随着它们而更新。所以查询生成 token 似乎没有效果?

我想到的一些可能的问题:

  • 唯一example我从 Apple 那里发现没有显示他们将它与获取的结果 Controller 一起使用(我在 SwiftUI 中使用 @FetchRequest,我几乎完全可以肯定它本质上是相同的),所以可能有影响?
  • .automaticallyMergeChangesFromParent 不应使用,我应该尝试合并策略,但这似乎也不起作用,从概念上讲,无论合并如何,查询生成 token 似乎都应该与此一起使用并固定到生成。

View 代码 - 处理从 View 上下文加载数据

// Environment object before fetch request necessary
// Passed in wherever main view is instantiated through .environment()
@Environment(\.managedObjectContext) var managedObjectContext

// Acts as fetched results controller, loading data automatically into items upon the managedObjectContext updating
// ExampleCoreDataEntity.retrieveItemsFetchRequest() is an extension method on the entity to easily get a fetch request for the type with sorting
@FetchRequest(fetchRequest: ExampleCoreDataEntity.retrieveItemsFetchRequest()) var items: FetchedResults<ExampleCoreDataEntity>

var body: some View {
    NavigationView {
        // Button to refresh and bring in changes
        Button(
            action: {
                do {
                    try self.managedObjectContext.setQueryGenerationFrom(.current)
                    self.managedObjectContext.refreshAllObjects()
                } catch {
                    print(error.localizedDescription)
                }
            },
            label: { Image(systemName: "arrow.clockwise") }
        )

        // Creates a table of items sorted by the entity itself (entities conform to Hashable)
        List(self.items, id: \.self) { item in
            Text(item.name ?? "")
        }
    }
}

SceneDelegate 中的代码(SwiftUI 应用程序启动的地方),我还初始化了 CoreData 所需的内容:

// Setup and pass in environment of managed object context to main view 
// via extension on persistent container that sets up CoreData stack
let managedObjectContext = NSPersistentContainer.shared.viewContext
do {
    try managedObjectContext.setQueryGenerationFrom(.current)
} catch {
    print(error.localizedDescription)
}
let view = MainView().environment(\.managedObjectContext, managedObjectContext)

// Setup background adding
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(backgroundCode), userInfo: nil, repeats: true)

// Setup window and pass in main view
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: view)

后台添加数据功能:

@objc func backgroundCode() {
    ExampleCoreDataEntity.create(names: ["background object"], in: backgroundContext, shouldSave: true)
}   

NSPersistentContainer 的设置:

extension NSPersistentContainer {

    private struct SharedContainerStorage {
        static let container: NSPersistentContainer = {
            let container = NSPersistentContainer(name: "Core_Data_Exploration")
            container.loadPersistentStores { (description, error) in
                guard error == nil else {
                    assertionFailure("CoreData: Unresolved error \(error!.localizedDescription)")
                    return
                }
                container.viewContext.automaticallyMergesChangesFromParent = true
            }
            return container
        }()
    }

    static var shared: NSPersistentContainer {
        return SharedContainerStorage.container
    }
}

在实体上创建/读取/更新/删除函数:

extension ExampleCoreDataEntity {
    static func retrieveItemsFetchRequest() -> NSFetchRequest<ExampleCoreDataEntity> {
        let request: NSFetchRequest<ExampleCoreDataEntity> = ExampleCoreDataEntity.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(keyPath: \ExampleCoreDataEntity.creationDate, ascending: false)]
        return request
    }

    static func create(names: [String], in context: NSManagedObjectContext, shouldSave save: Bool = false) {
        context.perform {
            names.forEach { name in
                let item = ExampleCoreDataEntity(context: context)
                item.name = name
                item.creationDate = Date()
                item.identifier = UUID()
            }

            do {
                if save {
                    try context.save()
                }
            } catch {
                // print error
            }
        }
    }

    func delete(in context: NSManagedObjectContext, shouldSave save: Bool = false) {
        context.perform {
            let name = self.name ?? "an item"

            context.delete(context.object(with: self.objectID))
            do {
                if save {
                    try context.save()
                }
            } catch {
                // print error
            }
        }
    }
}

最佳答案

问题是 container.viewContext.automaticallyMergesChangesFromParent = true

在使用查询生成标记时,该属性不能设置为 true。我回到这个问题并在 automaticallyMergesChangesFromParent 上面记录的 NSManagedObjectContext 的 header 中找到了这个:

Setting this property to YES when the context is pinned to a non-current query generation is not supported.

让它工作的一般流程如下:

  • 将查询生成 token 设置为 .current
  • 在 View 上下文中调用 .refreshAllObjects()
  • 在获取的结果 Controller 上调用 .performFetch()

这最后一部分违背了我在原始问题中使用 @FetchRequest 的代码 - 目前,我想不出一种看起来不太hacky的方法来手动重新获取.为了解决这个问题,我创建了一个中间存储类,其中包含一个采用其委托(delegate)协议(protocol)的 FetchedResultsController。该商店还采用了 ObservableObject,它允许 SwiftUI View 在采用商店的 ObservableObject 中调用 objectWillChange.send() 时监听其变化。

关于swift - 如何正确使用查询生成 token ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57483630/

相关文章:

带数组的 SwiftUI 动画形状

iOS 10 Xcode 8 - 迁移到 Swift 3 和 Date

iphone - 将 NSManagedObjects 添加到一对多关系

ios - 滚动时SwiftUI NavigationBar不会消失

SwiftUI Picker 压缩 Spacer()?

ios - 导航栏的右侧按钮丢失

iOS UICollectionReusableView 位于滚动指示器之上

ios - UISearchDisplayController、UITableView 和核心数据

ios - CoreData父子关系,一个CoreData子类可以和两个子类有相同的关系吗?

swiftui - 更改 iOS15 中列表的背景颜色