Swift UI + Core Data在后台线程?

sdnqo3pr  于 2023-06-21  发布在  Swift
关注(0)|答案(1)|浏览(117)

我在SwiftUI应用程序中有几个地方需要从CoreData获取(不断增长的)信息集,并在更新UI之前对其进行处理。
我使用MVVM方法来实现这一点,因此有许多NSFetchedResultsController示例负责获取和更新结果。
为了停止阻塞主线程,我尝试将NSFetchedResultsController转换为在后台线程上执行其工作。
只有当我禁用-com.apple.CoreData.ConcurrencyDebug 1参数以确保我没有违反线程规则时,这才有效。
我已经确保所有的CD访问都是在后台线程上完成的,但是当SwiftUI视图从CD对象访问属性时,它似乎是在主线程上进行的,因此导致崩溃。
现在我可以想到几个可能的解决方案:

  • 确保NSFetchedResultsController获取的数据可以在很短的时间内获取/处理,以确保UI不会挂起
  • 创建“DTO”对象,以便将获取的数据插入到后台线程内的另一个类示例中,并让UI使用它。
  • 这种方法的问题是,在对象上进行编辑或对更新做出React变得更加复杂,因为您需要手动保持CD和DTO对象同步。
    **编辑:**添加了我使用的ViewModel示例
class ContentViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {

    @Published var items: [Item] = []

    private let viewContext = PersistenceController.shared.container.viewContext
    private let resultsController: NSFetchedResultsController<Item>!
    private let backgroundContext: NSManagedObjectContext!

    override init() {
        let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
        let sort = NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)
        fetchRequest.sortDescriptors = [sort]
        fetchRequest.propertiesToFetch = ["timestamp"]

        backgroundContext = PersistenceController.shared.container.newBackgroundContext()
        backgroundContext.automaticallyMergesChangesFromParent = true

        resultsController = NSFetchedResultsController(
            fetchRequest: fetchRequest,
            managedObjectContext: backgroundContext,
            sectionNameKeyPath: nil,
            cacheName: nil)

        super.init()

        resultsController.delegate = self
        try? resultsController.performFetch()
        DispatchQueue.main.async { [self] in
            items = resultsController.fetchedObjects ?? []
        }
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        DispatchQueue.main.async { [self] in
            withAnimation {
                items = (controller.fetchedObjects as? [Item]) ?? []
            }
        }
    }
}
eoigrqb6

eoigrqb61#

核心问题是您在后台队列中获取项目,然后在主队列中使用它们。这是不允许的核心数据。如果没有并发调试标志,它可能不会立即崩溃,但它正在设置一个可能在某个时候发生的崩溃--这就是并发调试抱怨的原因。
要在一个队列上获取但在另一个队列上使用结果,可以使用对象ID。它们是线程安全的。你会用类似

@Published var itemIDs: [NSManagedObjectID] = []

接着是这样的

itemIDs = resultsController.fetchedObjects.map { $0.objectID } ?? []

然后在主队列中,按对象的ID查找对象。对于一个对象数组,您可以通过在 predicate 类似NSPredicate(format: "self in %@", itemIDs)的地方执行fetch来实现这一点。它应该是快的,因为在这一点上没有必要做任何过滤,因为内部核心数据缓存。

相关问题