Ios NSPERSISTENTCONTAINE&;具有大数据集的NSFetchedResultsController

Ios NSPERSISTENTCONTAINE&;具有大数据集的NSFetchedResultsController,ios,nsfetchedresultscontroller,nspersistentcontainer,Ios,Nsfetchedresultscontroller,Nspersistentcontainer,我们最近将应用程序切换到使用nspersistentcainer来设置核心数据堆栈。删除样板文件(如自动使用保存通知和合并处理)对我们很有吸引力,而且它的设置应该非常高效 然而,我们在导入大型数据集时面临一个问题。首先,我要告诉您,我们的数据模型相当复杂,有很多一对多的关系和实体 用于设置核心数据堆栈以使用连接到NSPersistentStoreCoordinator的专用队列NSManagedObjectContext,在后台队列上执行持久化。然后,主队列上下文将是该上下文的子上下文;专用队列

我们最近将应用程序切换到使用
nspersistentcainer
来设置核心数据堆栈。删除样板文件(如自动使用保存通知和合并处理)对我们很有吸引力,而且它的设置应该非常高效

然而,我们在导入大型数据集时面临一个问题。首先,我要告诉您,我们的数据模型相当复杂,有很多一对多的关系和实体

用于设置核心数据堆栈以使用连接到
NSPersistentStoreCoordinator
的专用队列
NSManagedObjectContext
,在后台队列上执行持久化。然后,主队列上下文将是该上下文的子上下文;专用队列上下文创建为主队列上下文的子级以处理保存。在
NSPersistentContainer
发明之前建立的一个相当标准

然而,当我们开始注意到随着数据集的增大,对应用程序进行分析会显示核心数据在主线程上占用了大量CPU时间。切换到
NSPersistentContainer
似乎可以解决这个问题。主线程上的活动要少得多。我们假定这是因为通过主队列的流量较少(因为
nPersistentContainer
的后台队列由
newBackgroundQueue()
提供,设置为直接保存到商店协调员;它们不是主队列上下文的子级)

在数据集增长之前,这一切似乎都很好。我们注意到,当处理大约15000条记录时(有时包含多达10-15000个与这些记录相关的对象),在保存后台上下文时,如果设置了
NSFetchedResultsController
,以观察这些对象,则UI将挂起。非常最多1分钟。显然,这是不可取的

下面是如何设置持久化容器:

...
    public init(storeURL: URL, modelName: String, configureStoreDescriptionHandler: ((NSPersistentStoreDescription, NSManagedObjectModel) -> ())? = nil) throws {
        guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "momd") else { throw StackError.modelNotFound }
        guard let model = NSManagedObjectModel(contentsOf: modelURL) else { throw StackError.modelNotCreated }

        let storeDescription = NSPersistentStoreDescription(url: storeURL)
        storeDescription.type = NSSQLiteStoreType

        configureStoreDescriptionHandler?(storeDescription, model)

        storeDescription.shouldMigrateStoreAutomatically = true
        storeDescription.shouldInferMappingModelAutomatically = true
        storeDescription.shouldAddStoreAsynchronously = false

        container = NSPersistentContainer(name: modelName, managedObjectModel: model)
        container.persistentStoreDescriptions = [storeDescription]

        var outError: StackError?
        container.loadPersistentStores { (storeDescription, error) in
            if let error = error {
                assertionFailure("Unable to load \(storeDescription) because \(error)")
                outError = .storeNotMigrated
            }
        }

        if let error = outError {
            throw error
        }

        container.viewContext.automaticallyMergesChangesFromParent = true
    }

    public var mainQueueManagedObjectContext: NSManagedObjectContext {
        return container.viewContext
    }

    public func newPrivateQueueContext() -> NSManagedObjectContext {
        let context = container.newBackgroundContext()
        return context
    }
...
我们通过
newPrivateQueueContext()
获取私有队列上下文,执行我们的工作,然后保存。大型数据集导致
NSFetchedResultsController
挂起

Apple建议设置
viewContext.automaticallyMergesChangesFromParent=true
,直接保存到持久存储比在父子配置中保存到中间人(视图上下文)更有效:

这两个上下文都连接到同一个persistentStoreCoordinator,它作为其父上下文用于数据合并。这比在父上下文和子上下文之间合并更有效

实际上,我们已经设法解决了这个问题,方法是删除
automaticallyMergesChangesFromParent=true
,并对私有队列上下文的配置方式进行以下更改:

...
    public var mainQueueManagedObjectContext: NSManagedObjectContext {
        return container.viewContext
    }

    public func newPrivateQueueContext() -> NSManagedObjectContext {
        let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        context.parent = container.viewContext

        NotificationCenter.default.addObserver(self, selector: #selector(handlePrivateQueueContextDidSaveNotification(_:)), name: .NSManagedObjectContextDidSave, object: context)

        return context
    }

    @objc func handlePrivateQueueContextDidSaveNotification(_ note: Notification) {
        container.viewContext.performAndWait {
            try? container.viewContext.save()
        }
    }
...
事实上,这在父子配置中配置了我们的主上下文和子上下文——据苹果公司称,这种配置效率较低

这管用!数据已正确保存到磁盘(已验证),数据有效(已验证),并且不再有
NSFetchedResultsController
挂起

然而,这提出了几个问题:

  • 为什么苹果推荐的设置
    NSPersistentContainer
    的方法会导致在处理大型数据集时锁定主队列?它不是应该更有效率吗?我们有什么遗漏吗
  • 有没有人遇到过这样的问题,也许用不同的方式解决了?我们找不到关于设置
    NSPersistentContainer
    在线处理大型数据集的详细信息
  • 你能看到我们设置堆栈的方式有什么问题吗 也许建议对配置进行改进
  • 看起来好像是直接保存到持久性存储,然后合并中的更改比父子配置效率低?有人能解释一下吗

我应该补充一点,我们试图通过设置
fetchBatchSize
和改进谓词来提高我们的
NSFetchedResultsController
的效率,但没有效果。

如果您找到了答案,请您解释一下您是如何克服这个问题的