使用performBackgroundTask更新NSFetchedResultsController

时间:2016-11-30 15:28:42

标签: ios swift core-data

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

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

有两件事我不明白:

  1. 我原本希望这会修改,但不能保存父上下文。但是,父上下文肯定会被保存(通过手动打开SQLite文件进行验证)。
  2. 我希望在后台内容保存回其父级时更新NSFetchedResultsController,但这不会发生。我是否需要在主线程上手动触发某些内容?
  3. 显然我有一些东西没有得到。有人可以解释一下吗?

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

3 个答案:

答案 0 :(得分:28)

说明

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

无论您调用哪种方法,在任何一种情况下,(返回的)临时NSManagedObjectContext都设置为privateQueueConcurrencyType并且与NSPersistentStoreCoordinator直接关联,因此没有{{1 }}

请参阅documentation

  

调用此方法会导致持久性容器创建和   返回一个设置为concurrencyType的新NSManagedObjectContext   privateQueueConcurrencyType。这个新的背景将与之相关联   NSPersistentStoreCoordinator直接设置为使用   NSManagedObjectContextDidSave自动广播。

...或自己确认一下:

parent

由于缺少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...>) ,更改不会提交给parent,例如如果parent contextviewContext未触动,则连接的viewContext将无法识别任何更改,因此不会更新或调用其NSFetchedResultsController方法。相反,更改将直接推送到delegate,然后保存到persistent store coordinator

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

解决方案

通过使用两个具有父子关系的persistent store来实现您所描述的行为:

NSManagedObjectContext

但是,您也可以坚持使用// 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() } } 或使用performBackgroundTask(_:)。但如前所述,在这种情况下,更改将直接保存到持久性存储中,并且默认情况下不会更新newBackgroundContext()。为了将 down 的更改传播到viewContext,导致viewContext收到通知,您必须将NSFetchedResultsController设置为viewContext.automaticallyMergesChangesFromParent

true

请注意,一次性添加10.000个对象等大量更改可能会导致// 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() } 疯狂,从而阻止NSFetchedResultsController

答案 1 :(得分:1)

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

尝试添加这一行:

persistentContainer.viewContext.automaticallyMergesChangesFromParent = true

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

答案 2 :(得分:0)

这在我的项目中完美地起作用。  在函数updateEnglishNewsListener(:)中,这里的参数数据在anyobject中,我进一步将它转换为json formate以便保存。

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

有三种类型 1. ConfinementConcurrencyType 2. PrivateQueueConcurrencyType 3. MainQueueConcurrencyType

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

在updateEnglishNewsListener(:)函数中,params数据是您的输入。 (data-&gt;您要更新的数据。)

 private func updateEnglishNewsListener(data: [AnyObject] ){

            //Here is your data

            let privateAsyncMOC_En = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
            // The context is associated with the main queue, and as such is tied into the application’s 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.

                   privateAsyncMOC_En.parent = managedObjectContext
                    privateAsyncMOC_En.perform{
                        // The perform(_:) method returns immediately and the context executes the block methods on its own thread. Here it use background thread.

                        let convetedJSonData = self.convertAnyobjectToJSON(anyObject: data as AnyObject)
                        for (_ ,object) in convetedJSonData{
                            self.checkIFNewsIdForEnglishAlreadyExists(newsId: object["news_id"].intValue, completion: { (count) in

                        if count != 0{
                                self.updateDataBaseOfEnglishNews(json: object, newsId: object["news_id"].intValue)
                            }
                        })
                    }
                    do {
                        if privateAsyncMOC_En.hasChanges{

                        try privateAsyncMOC_En.save()

                    }
                    if managedObjectContext.hasChanges{

                        try managedObjectContext.save()

                    }

                }catch {
                    print(error)
                }
            }
    }

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

    private func checkIFNewsIdForEnglishAlreadyExists(newsId:Int,completion:(_ count:Int)->()){

        let fetchReq:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()
        fetchReq.predicate = NSPredicate(format: "news_id = %d",newsId)
        fetchReq.fetchLimit = 1 // this gives one data at a time for checking coming data to saved data

        do {
            let count = try managedObjectContext.count(for: fetchReq)
            completion(count)

        }catch{
            let error  = error as NSError
            print("\(error)")
            completion(0)
        }


    }

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

    private func updateDataBaseOfEnglishNews(json: JSON, newsId : Int){

        do {
            let fetchRequest:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()

            fetchRequest.predicate = NSPredicate(format: "news_id = %d",newsId)


            let fetchResults = try  managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as? [TestEntity]
            if let fetchResults = fetchResults {

                if fetchResults.count != 0{
                    let newManagedObject = fetchResults[0]
                    newManagedObject.setValue(json["category_name"].stringValue, forKey: "category_name")
                    newManagedObject.setValue(json["description"].stringValue, forKey: "description1")

                    do {
                        if ((newManagedObject.managedObjectContext?.hasChanges) != nil){

                            try newManagedObject.managedObjectContext?.save()

                        }

                    } catch {
                        let saveError = error as NSError
                        print(saveError)
                    }
                }

            }

        } catch {

            let saveError = error as NSError
            print(saveError)
        }
    }

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

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

希望它会对你有所帮助。如果有任何混淆,请询问。