我有一个NSFetchedResultsController
,我正在尝试更新背景上下文中的数据。例如,这里我试图删除一个对象:
persistentContainer.performBackgroundTask { context in
let object = context.object(with: restaurant.objectID)
context.delete(object)
try? context.save()
}
有两件事我不明白:
NSFetchedResultsController
,但这不会发生。我是否需要在主线程上手动触发某些内容?显然我有一些东西没有得到。有人可以解释一下吗?
我知道我已正确实现了获取的结果控制器委托方法,因为如果我将代码更改为直接更新viewContext
,则一切都按预期工作。
答案 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 context
和viewContext
未触动,则连接的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
}
希望它会对你有所帮助。如果有任何混淆,请询问。