使用performBackgroundTask更新NSFetchedResultsController
我有一个NSFetchedResultsController
,我试图更新我的数据在背景上下文。 例如,在这里我试图删除一个对象:
persistentContainer.performBackgroundTask { context in let object = context.object(with: restaurant.objectID) context.delete(object) try? context.save() }
有两件事我不明白:
- 我会期望这个修改, 但不保存父上下文。 但是,父上下文肯定被保存(通过手动打开SQLite文件来validation)。
- 我会期望
NSFetchedResultsController
更新时,背景内容保存备份到其父,但没有发生。 我是否需要在主线程上手动触发某些内容?
显然有一些我没有得到。 有人可以解释吗?
我知道我已经正确实现了获取的结果控制器委托方法,因为如果我更改我的代码直接更新viewContext
,一切都按预期工作。
说明
NSPersistentContainer
的实例方法performBackgroundTask(_:)
和newBackgroundContext()
的logging很差。
无论您调用哪种方法,在任何一种情况下(返回的)临时NSManagedObjectContext
都使用privateQueueConcurrencyType
设置,并直接与privateQueueConcurrencyType
关联,因此没有parent
。
请参阅文档 :
调用此方法将导致持久容器创build并返回一个新的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...>)
由于缺lessparent
,所以更改将不会被提交给parent context
,例如viewContext
和viewContext
不变,连接的viewContext
将不会识别任何更改,因此不会更新或调用其delegate
的方法。 而是将更改直接推送到persistent store coordinator
,然后将其保存到persistent store
。
我希望,我能够帮助你,如果你需要进一步的帮助,我可以补充一下,如何按照你所描述的那样达到我想要的行为。 ( 解决scheme添加如下)
解
通过使用具有父子关系的两个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()
。 但是如前所述,在这种情况下,更改将直接保存到持久性存储中,并且viewContext
默认情况下不会更新。 为了将更改传播到viewContext
(这会导致通知viewContext
,必须将viewContext.automaticallyMergesChangesFromParent
设置为true
:
// 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() }
请注意,大量更改(例如,一次添加10.000个对象)可能会导致您的NSFetchedResultsController
疯狂,从而阻塞main queue
。
这在我的项目中完美地工作。 在函数updateEnglishNewsListener(:)中,这里的参数数据是在任何对象,我进一步转换成JSON甲酸为了节省的目的。
核心数据使用线程(或序列化队列)限制来保护被pipe理对象和被pipe理对象上下文(参见核心数据编程指南)。 这样做的结果是上下文假设默认所有者是分配它的线程或队列 – 这是由调用其init方法的线程决定的。 因此,您不应该在一个线程上初始化上下文,然后将其传递给另一个线程。
有三种types1. ConfinementConcurrencyType 2. PrivateQueueConcurrencyType 3. MainQueueConcurrencyType
MainQueueConcurrencyType创build一个与主队列相关联的上下文,这对NSFetchedResultsController来说非常合适。
在updateEnglishNewsListener(:)函数中,params数据是你的input。 (数据 – >你想要更新的数据。)
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) } }
根据需要将旧数据replace为新数据。
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) } }
将anyobject转换为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 }
希望它会帮助你。 如果有任何困惑,那么请问。
视图上下文不会更新,除非您已将其设置为自动合并父更改。 viewContext已经被设置为你从NSPersistentContainer接收到的任何backgroundContext的子元素。
尝试添加这一行:
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
现在,viewContext会在保存backgroundContext后更新,这将触发NSFetchedResultsController更新。