使用performBackgroundTask更新NSFetchedResultsController

gan*_*ogo 16 core-data ios swift

我有一个NSFetchedResultsController,我试图在背景上下文更新我的数据.例如,这里我试图删除一个对象:

persistentContainer.performBackgroundTask { context in
  let object = context.object(with: restaurant.objectID)
  context.delete(object)
  try? context.save()
}
Run Code Online (Sandbox Code Playgroud)

有两件事我不明白:

  1. 我本来希望这可以修改,但不能保存父上下文.但是,父上下文肯定会被保存(通过手动打开SQLite文件进行验证).
  2. 我希望NSFetchedResultsController在后台内容保存回其父级时更新,但这不会发生.我是否需要在主线程上手动触发某些内容?

显然有一些我没有得到的东西.任何人都能解释一下吗?

我知道我已经正确地实现了获取的结果控制器委托方法,因为如果我改变我的代码直接更新viewContext,一切都按预期工作.

Tob*_*obi 31

说明

NSPersistentContainer的实例方法performBackgroundTask(_:)并且newBackgroundContext()记录很少.

无论你调用哪种方法,在任何一种情况下,(返回的)临时NSManagedObjectContext设置privateQueueConcurrencyType都与之NSPersistentStoreCoordinator直接相关,因此没有parent.

文档:

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

...或自己确认:

persistentContainer.performBackgroundTask { (context) in
    print(context.parent) // nil
    print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
}

let context = persistentContainer.newBackgroundContext()
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
Run Code Online (Sandbox Code Playgroud)

由于缺少a parent,更改不会被提交到parent context例如,viewContext并且在viewContext未触及的情况下,连接NSFetchedResultsController将不会识别任何更改,因此不会更新或调用其delegate方法.相反,更改将被直接推送到persistent store coordinator之后保存到persistent store.

我希望,我能够帮助你,如果你需要进一步的帮助,我可以补充一下,如你所说,如何获得我想要的行为.(下面添加解决方案)

您可以通过使用NSManagedObjectContext具有父子关系的两个s 来实现您所描述的行为:

// Create new context for asynchronous execution with privateQueueConcurrencyType  
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Add your viewContext as parent, therefore changes are pushed to the viewContext, instead of the persistent store coordinator
let viewContext = persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.perform {
    // Do your work...
    let object = backgroundContext.object(with: restaurant.objectID)
    backgroundContext.delete(object)
    // Propagate changes to the viewContext -> fetched results controller will be notified as a consequence
    try? backgroundContext.save()
    viewContext.performAndWait {
        // Save viewContext on the main queue in order to store changes persistently
        try? viewContext.save()
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,您也可以坚持使用performBackgroundTask(_:)或使用newBackgroundContext().但如前所述,在这种情况下,更改将直接保存到持久性存储中,并且viewContext默认情况下不会更新.为了改变传播下来viewContext,这将导致NSFetchedResultsController通知,你必须设置viewContext.automaticallyMergesChangesFromParenttrue:

// Set automaticallyMergesChangesFromParent to true
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
persistentContainer.performBackgroundTask { context in
    // Do your work...
    let object = context.object(with: restaurant.objectID)
    context.delete(object)
    // Save changes to persistent store, update viewContext and notify fetched results controller
    try? context.save()
}
Run Code Online (Sandbox Code Playgroud)

请注意,一次性添加10.000个对象等大量更改可能会让您NSFetchedResultsController发疯,因此会阻止main queue.

  • automaticMergesChangesFromParent解决方案是Apple似乎正在推动的解决方案.实际上在Docs中,想法是默认情况下automaticMergesChangesFromParent是真的,但似乎并非如此,需要布尔值.文档指定现在生成viewContext并且应该是readOnly:"与主队列关联的托管对象上下文.(只读),此上下文直接与NSPersistentStoreCoordinator关联,默认情况下是非生成的." (2认同)

And*_*ons 6

除非您将视图上下文设置为自动合并来自父级的更改,否则视图上下文不会更新。viewContext 已经设置为您从 NSPersistentContainer 收到的任何 backgroundContext 的子级。

尝试只添加这一行:

persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
Run Code Online (Sandbox Code Playgroud)

现在,viewContext 将在 backgroundContext 保存后更新,这将触发 NSFetchedResultsController 更新。


Amr*_*ari 0

这在我的项目中非常有效。 \n 在函数 updateEnglishNewsListener(:) 中,这里参数数据位于任何对象中,我进一步将其转换为 json 格式以进行保存。

\n\n

Core Data 使用线程(或序列化队列)限制来保护托管对象和托管对象上下文(请参阅 Core Data 编程指南)。这样做的结果是,上下文假定默认所有者是分配它的线程或队列\xe2\x80\x94,这是由调用其 init 方法的线程确定的。因此,您不应在一个线程上初始化上下文,然后将其传递给另一线程。

\n\n

共有三种类型\n1。ConfinementConcurrencyType\n2. PrivateQueueConcurrencyType\n3. 主队列并发类型

\n\n

MainQueueConcurrencyType 创建与主队列关联的上下文,非常适合与 NSFetchedResultsController 一起使用。

\n\n

在 updateEnglishNewsListener(:) 函数中,参数数据是您的输入。(数据->您要更新的数据。)

\n\n
 private func updateEnglishNewsListener(data: [AnyObject] ){\n\n            //Here is your data\n\n            let privateAsyncMOC_En = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)\n            // The context is associated with the main queue, and as such is tied into the application\xe2\x80\x99s event loop, but it is otherwise similar to a private queue-based context. You use this queue type for contexts linked to controllers and UI objects that are required to be used only on the main thread.\n\n                   privateAsyncMOC_En.parent = managedObjectContext\n                    privateAsyncMOC_En.perform{\n                        // The perform(_:) method returns immediately and the context executes the block methods on its own thread. Here it use background thread.\n\n                        let convetedJSonData = self.convertAnyobjectToJSON(anyObject: data as AnyObject)\n                        for (_ ,object) in convetedJSonData{\n                            self.checkIFNewsIdForEnglishAlreadyExists(newsId: object["news_id"].intValue, completion: { (count) in\n\n                        if count != 0{\n                                self.updateDataBaseOfEnglishNews(json: object, newsId: object["news_id"].intValue)\n                            }\n                        })\n                    }\n                    do {\n                        if privateAsyncMOC_En.hasChanges{\n\n                        try privateAsyncMOC_En.save()\n\n                    }\n                    if managedObjectContext.hasChanges{\n\n                        try managedObjectContext.save()\n\n                    }\n\n                }catch {\n                    print(error)\n                }\n            }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

检查coredata中是否已经存在数据,避免数据冗余。Coredata没有主键概念,因此我们依次检查coredata中是否已存在数据。当且仅当更新数据已存在于 coredata 中时,数据才会被更新。这里 checkIFNewsIdForEnglishAlreadyExists(:) 函数返回 0 或 value 。如果返回 0,则数据不会保存在数据库中,否则会保存。我正在使用完成句柄来了解新数据或旧数据。

\n\n
    private func checkIFNewsIdForEnglishAlreadyExists(newsId:Int,completion:(_ count:Int)->()){\n\n        let fetchReq:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()\n        fetchReq.predicate = NSPredicate(format: "news_id = %d",newsId)\n        fetchReq.fetchLimit = 1 // this gives one data at a time for checking coming data to saved data\n\n        do {\n            let count = try managedObjectContext.count(for: fetchReq)\n            completion(count)\n\n        }catch{\n            let error  = error as NSError\n            print("\\(error)")\n            completion(0)\n        }\n\n\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

根据需要将旧数据替换为新数据。

\n\n
    private func updateDataBaseOfEnglishNews(json: JSON, newsId : Int){\n\n        do {\n            let fetchRequest:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()\n\n            fetchRequest.predicate = NSPredicate(format: "news_id = %d",newsId)\n\n\n            let fetchResults = try  managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as? [TestEntity]\n            if let fetchResults = fetchResults {\n\n                if fetchResults.count != 0{\n                    let newManagedObject = fetchResults[0]\n                    newManagedObject.setValue(json["category_name"].stringValue, forKey: "category_name")\n                    newManagedObject.setValue(json["description"].stringValue, forKey: "description1")\n\n                    do {\n                        if ((newManagedObject.managedObjectContext?.hasChanges) != nil){\n\n                            try newManagedObject.managedObjectContext?.save()\n\n                        }\n\n                    } catch {\n                        let saveError = error as NSError\n                        print(saveError)\n                    }\n                }\n\n            }\n\n        } catch {\n\n            let saveError = error as NSError\n            print(saveError)\n        }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

将任何对象转换为 JSON 以保存在 coredata 中

\n\n
    func convertAnyobjectToJSON(anyObject: AnyObject) -> JSON{\n        let jsonData = try! JSONSerialization.data(withJSONObject: anyObject, options: JSONSerialization.WritingOptions.prettyPrinted)\n        let jsonString = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String\n        if let dataFromString = jsonString.data(using: String.Encoding.utf8, allowLossyConversion: false) {\n            let json = JSON(data: dataFromString)\n            return json\n        }\n        return nil\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

希望它能帮助你。如果有任何困惑,请询问。

\n