tutorial query existing example espaƱol efecto data apple ios swift core-data

ios - query - Actualice NSFetchedResultsController utilizando performBackgroundTask



swift 4 core data relationships (3)

Tengo un NSFetchedResultsController y estoy tratando de actualizar mis datos en un contexto de fondo. Por ejemplo, aquí estoy tratando de eliminar un objeto:

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

Hay 2 cosas que no entiendo:

  1. Hubiera esperado que esto modificara, pero no guardara el contexto principal. Sin embargo, el contexto primario definitivamente se guarda (como se verificó al abrir manualmente el archivo SQLite).
  2. Hubiera esperado que NSFetchedResultsController actualizase cuando el contenido de fondo se guarda nuevamente en su copia principal, pero esto no está sucediendo. ¿Debo activar manualmente algo en el hilo principal?

Obviamente, hay algo que no entiendo. ¿Alguien puede explicar esto?

Sé que he implementado los métodos de delegado del controlador de resultados obtenidos correctamente, porque si cambio mi código para actualizar directamente el viewContext , todo funciona como se esperaba.


Esto me funciona perfectamente en mi proyecto. En la función updateEnglishNewsListener (:), aquí los datos de parámetros están en anyobject y luego los convierto en json formato para propósitos de ahorro.

Los datos principales usan confinamiento de subprocesos (o cola serializada) para proteger los objetos administrados y los contextos de objetos gestionados (consulte la Guía de programación de datos básicos). Una consecuencia de esto es que un contexto asume que el propietario predeterminado es el hilo o la cola que lo asignó, esto está determinado por el hilo que llama a su método init. Por lo tanto, no debe inicializar un contexto en un hilo y luego pasarlo a un hilo diferente.

Hay tres tipos 1. ConfinementConcurrencyType 2. PrivateQueueConcurrencyType 3. MainQueueConcurrencyType

MainQueueConcurrencyType crea un contexto asociado a la cola principal que es perfecto para usar con NSFetchedResultsController.

En la función updateEnglishNewsListener (:), los datos de params son su entrada. (datos-> Datos que quiere actualizar)

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) } } }

La comprobación de datos ya existe en coredata o no para evitar los datos de redundancia. Los Coredatos no tienen el concepto de clave primaria, por lo que verificamos en forma secuencial si los datos ya existen en Coredata o no. Los datos se actualizan solo si la actualización de datos ya existe en coredata. Aquí la función checkIFNewsIdForEnglishAlreadyExists (:) devuelve 0 o valor. Si devuelve 0, entonces los datos no se guardan en la base de datos más guardada. Estoy usando el control de finalización para conocer los datos nuevos o viejos.

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) } }

Reemplazar los datos antiguos por uno nuevo de acuerdo con los requisitos.

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) } }

Convierta anyobject a JSON para guardar en 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 }

Espero que te ayude. Si hay alguna confusión, por favor pregunte.


Explicación

NSPersistentContainer métodos de instancia performBackgroundTask(_:) y newBackgroundContext() están poco documentados.

NSManagedObjectContext método al que llame, en cualquier caso el NSManagedObjectContext temporal (devuelto) se configura con privateQueueConcurrencyType y está asociado con NSPersistentStoreCoordinator directamente y, por lo tanto, no tiene parent .

Ver documentación :

La invocación de este método hace que el contenedor persistente cree y devuelva un nuevo NSManagedObjectContext con concurrencyType establecido en privateQueueConcurrencyType. Este nuevo contexto se asociará directamente con NSPersistentStoreCoordinator y está configurado para consumir transmisiones NSManagedObjectContextDidSave automáticamente.

... o confirme usted mismo:

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...>)

Debido a la falta de un parent , los cambios no se comprometerán con un parent context como, por ejemplo, el viewContext y con viewContext no viewContext , un NSFetchedResultsController conectado no reconocerá ningún cambio y, por lo tanto, no actualiza ni llama a los métodos de su delegate . . En su lugar, los cambios se enviarán directamente al persistent store coordinator y luego se guardarán en la persistent store .

Espero que pueda ayudarlo y, si necesita más ayuda, puedo agregar cómo puedo obtener el comportamiento deseado, como lo describió usted, en mi respuesta. ( Solución agregada a continuación)

Solución

NSManagedObjectContext el comportamiento, tal como lo describe usted, utilizando dos NSManagedObjectContext con una relación padre-hijo:

// 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() } }

Sin embargo, también puede seguir con performBackgroundTask(_:) o usar newBackgroundContext() . Pero como se dijo anteriormente, en este caso los cambios se guardan directamente en la tienda persistente y viewContext no se actualiza de forma predeterminada. Para propagar los cambios al viewContext , que hace que se notifique NSFetchedResultsController , debe establecer viewContext.automaticallyMergesChangesFromParent en 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() }

Tenga en cuenta que los cambios extensos, como agregar 10.000 objetos a la vez, probablemente harán que su NSFetchedResultsController loco y, por lo tanto, bloqueará la main queue .


El contexto de vista no se actualizará a menos que lo haya configurado para fusionar automáticamente los cambios desde el elemento primario. ViewContext ya está configurado como secundario de cualquier fondoContext que reciba del NSPersistentContainer.

Intenta agregar solo esta línea:

persistentContainer.viewContext.automaticallyMergesChangesFromParent = true

Ahora, viewContext se actualizará después de que backgroundContext se haya guardado y esto ACTIVARÁ el NSFetchedResultsController para actualizar.