Datos centrales en detalle

Recientemente, comencé a trabajar en un gran proyecto utilizando Core Data. Lo habitual es que las personas en los proyectos cambien, se pierda la experiencia y se olviden los matices. Es imposible profundizar en el estudio de un marco específico: todos tienen sus propios problemas de trabajo. Por lo tanto, preparé una breve presentación, a partir de aquellos puntos que considero importantes o insuficientemente cubiertos en los tutoriales. Comparto con todos y espero que esto ayude a escribir código efectivo y no cometer errores. Se supone que ya estás un poco en el tema.

Comenzaré con lo banal.

Core Data es un marco que gestiona y almacena datos en una aplicación. Puede considerar Core Data como un contenedor sobre un almacenamiento relacional físico que representa datos en forma de objetos, mientras que Core Data en sí no es una base de datos.

Objetos de datos principales


imagen

Para crear almacenamiento, la aplicación usa las clases NSPersistentStoreCoordinator o NSPersistentContainer . NSPersistentStoreCoordinator crea un almacenamiento del tipo especificado según el modelo, puede especificar la ubicación y las opciones adicionales. NSPersistentContainer se puede usar con iOS10, proporciona la capacidad de crear con una cantidad mínima de código.

Funciona de la siguiente manera: si existe una base de datos en la ruta especificada, el coordinador verifica su versión y, si es necesario, realiza una migración. Si la base de datos no existe, se crea según el modelo NSManagedObjectModel. Para que todo esto funcione correctamente, antes de realizar cambios en el modelo, cree una nueva versión en Xcode a través del menú Editor -> Agregar versión del modelo. Si obtiene la ruta, puede encontrar y abrir la base en el emulador.

Ejemplo con NSPersistentStoreCoordinator
var persistentCoordinator: NSPersistentStoreCoordinator = { let modelURL = Bundle.main.url(forResource: "Test", withExtension: "momd") let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL!) let persistentCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!) let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] let storeURL = URL(fileURLWithPath: documentsPath.appending("/Test.sqlite")) print("storeUrl = \(storeURL)") do { try persistentCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: [NSSQLitePragmasOption: ["journal_mode":"MEMORY"]]) return persistentCoordinator } catch { abort() } } () 

Ejemplo de NSPersistentContainer
 var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "CoreDataTest") container.loadPersistentStores(completionHandler: { (storeDescription, error) in print("storeDescription = \(storeDescription)") if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container } () 


Core Data utiliza 4 tipos de almacenamiento:

- SQLite
- binario
- en memoria
- XML ​​(solo Mac OS)

Si, por ejemplo, por razones de seguridad, no desea almacenar datos en forma de archivo, pero al mismo tiempo desea utilizar el almacenamiento en caché durante la sesión y los datos en forma de objetos, el almacenamiento de tipo "en memoria" es bastante adecuado. En realidad, no está prohibido tener varios tipos diferentes de almacenamiento en una sola aplicación.

Quiero decir algunas palabras sobre el objeto NSManagedObjectContext . En general, Apple proporciona una redacción muy vaga para NSManagedObjectContext, un entorno para trabajar con objetos Core Data. Todo esto por el deseo de disociarse de las asociaciones con bases de datos relacionales y presentar Core Data como una herramienta fácil de usar que no requiere una comprensión de las claves, transacciones y otros atributos de Bazdan. Pero en el lenguaje de las bases de datos relacionales, NSManagedObjectContext puede, en cierto sentido, llamarse administrador de transacciones. Probablemente haya notado que tiene métodos de guardar y revertir, aunque lo más probable es que use solo el primero.

Un malentendido de este simple hecho lleva al uso de un modelo de contexto único, incluso en situaciones donde no es suficiente. Por ejemplo, está editando un documento grande y, al mismo tiempo, tuvo que descargar un par de directorios. ¿En qué punto llamas a guardar? Si estuviéramos trabajando con una base de datos relacional, entonces no habría dudas, ya que cada operación se realizaría en su propia transacción. Core Data también tiene una forma muy conveniente de resolver este problema: esta es una rama del contexto secundario. Pero desafortunadamente, por alguna razón esto rara vez se usa. Aquí hay un buen artículo sobre este tema.

Herencia


Por alguna razón que no entiendo, hay una gran cantidad de manuales y ejemplos en los que la herencia para Entity / NSManagedObject (tablas) no se usa de ninguna manera. Mientras tanto, es una herramienta muy conveniente. Si no usa la herencia, puede asignar valores a los atributos (campos) solo a través del mecanismo KVC, que no verifica los nombres y tipos de atributos, y esto puede conducir fácilmente a errores de tiempo de ejecución.

La redefinición de clase para NSManagedObject se realiza en el diseñador de Core Data:

imagen

Herencia y generación de código


Después de especificar el nombre de clase para Entity, puede usar la generación de código y obtener una clase con código listo:

imagen

imagen

Si desea ver el código autogenerado, pero no desea agregar archivos al proyecto, puede usar otra forma: configure la opción "Codegen" para Entity. En este caso, el código debe buscarse en ../ DerivedData / ...

imagen

Utilice la generación de código para crear clases, los errores tipográficos en los nombres de variables pueden provocar errores de tiempo de ejecución.

Aquí hay un código como este:

 @objc public class Company: NSManagedObject {    @NSManaged public var inn: String?  @NSManaged public var name: String?  @NSManaged public var uid: String?  @NSManaged public var employee: NSSet? } 

En swift, @NSManaged tiene el mismo significado que dinámico en Objective C.
Core Data se encarga de recibir datos (tiene accesores internos) para los atributos de sus clases. Si tiene campos de tránsito, debe agregar funciones para calcularlos.

Las clases heredadas de NSManagedObject (tablas) no tenían un constructor "regular" antes de IOS10, a diferencia de otras clases. Para crear un objeto de tipo Compañía, fue necesario escribir una construcción bastante torpe usando NSEntityDescription. Ahora hay un método más conveniente de inicialización a través del contexto (NSManagedObjectContext). El código está abajo. Tenga en cuenta la ventaja de la herencia al asignar atributos sobre el mecanismo KVC:

 // 1 -    NSEntityDescription,    KVO let company1 = NSEntityDescription.insertNewObject(forEntityName: "Company", into: moc) company1.setValue("077456789111", forKey: "inn") company1.setValue(" ", forKey: "name") // 2 -    NSEntityDescription,     let company2 = NSEntityDescription.insertNewObject(forEntityName: "Company", into: moc) as! Company company2.inn = "077456789222" company2.name = " " // 3 -     (IOS10+),     let company3 = Company(context: moc) company3.inn = "077456789222" company3.name = " " 

Espacio de nombres para NSManagedObject


Otra cosa que vale la pena mencionar es el espacio de nombres.

imagen

No tendrá dificultades si está trabajando en ObjectiveC o Swift. Por lo general, este campo se completa correctamente de forma predeterminada. Pero en proyectos mixtos, puede ser una sorpresa para ti que para las clases en Swift y ObjectiveC necesites poner diferentes opciones. En Swift, el "Módulo" debe estar lleno. Si este campo no se completa, se agregará un prefijo con el nombre del proyecto al nombre de la clase, lo que provocará un error de tiempo de ejecución. En el Objetivo C, deje el "Módulo" vacío, de lo contrario no se encontrará NSManagedObject cuando acceda a él a través del nombre de la clase.

Enlaces entre objetos


En principio, el tema de las relaciones está bien cubierto, pero quiero centrarme en cómo agregar entidades secundarias a los padres. Por lo tanto, primero, un recordatorio rápido del mecanismo para crear enlaces. Considere un ejemplo tradicional, una empresa son empleados, una relación de uno a muchos:

  • Crear una conexión en cada lado (tabla)
  • Después de eso, el campo Inverso está disponible, debe completarse en cada tabla.

imagen

imagen

Apple insiste en especificar relaciones inversas. Al mismo tiempo, la inversión no fortalece la coherencia, pero ayuda a que Core Data rastree los cambios en ambos lados de la conexión, es importante para el almacenamiento en caché y la actualización de la información.

También es importante especificar la regla de eliminación correctamente. Una regla de eliminación es una acción que se realizará con este objeto cuando se elimine el objeto primario.

  • Cascada: eliminación de todos los objetos secundarios, cuando se elimina el elemento primario.
  • Denegar: prohibición de eliminar a un padre si hay un hijo
  • Anular - anular referencia principal
  • Sin acción: no se especifica ninguna acción, dará una advertencia en la compilación

En este ejemplo, cuando se elimina una empresa, se eliminarán todos los empleados (en cascada). Cuando eliminas a un empleado, el enlace a él en la empresa se restablecerá (pantalla previa)

Formas de agregar entidades secundarias al padre


1) El primer método es agregar a través de NSSet. Por ejemplo, agregue 2 empleados a la empresa:

 let set = NSMutableSet();    if let employee1 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee1.firstName = "" employee1.secondName = "" set.add(employee1) } if let emploee2 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee2.firstName = "" employee2.secondName = "" set.add(employee2) }    company.employee = set 

Este método es conveniente para inicializar el objeto o llenar la base de datos. Hay un ligero matiz. Si la compañía ya tenía empleados, y usted asignó un nuevo conjunto, los antiguos empleados restablecerán el enlace a la compañía, pero no se eliminarán. Alternativamente, puede obtener una lista de empleados y trabajar ya con este conjunto.

 let set = company.mutableSetValue(forKey: "employee") 

2) Agregar objetos secundarios a través de la identificación principal

 if let employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee { employee.firstName = "" employee.secondName = "" employee.company = company } 

El segundo método es conveniente al agregar o editar un objeto secundario en
forma separada

3) Agregar objetos secundarios mediante métodos generados automáticamente

 extension Company {    @objc(addEmployeeObject:)  @NSManaged public func addEmployee(_ value: Employee)    @objc(removeEmployeeObject:)  @NSManaged public func removeFromEmployee(_ value: Employee)    @objc(addEmployee:)  @NSManaged public func addEmployee(_ values: NSSet)    @objc(removeEmployee:)  @NSManaged public func removeFromEmployee(_ values: NSSet) } 

En aras de la exhaustividad, es útil saber acerca de este método, pero de alguna manera no me fue útil, y elimino el código adicional para no saturar el proyecto.

Consultas de cláusula secundaria


En Core Data, no puede realizar una consulta arbitraria entre ningún dato, como podemos hacer en SQL. Pero entre objetos dependientes, es fácil rastrear usando un predicado estándar. A continuación se muestra un ejemplo de una consulta que selecciona todas las empresas en las que hay un empleado con el nombre especificado:

 public static func getCompanyWithEmployee(name: String) -> [Company] { let request = NSFetchRequest<NSFetchRequestResult>(entityName: self.className()) request.predicate = NSPredicate(format: "ANY employee.firstName = %@", name) do { if let result = try moc.fetch(request) as? [Company] { return result } } catch { } return [Company]() } 

La llamada al método en el código se verá así:

 //  ,    let companies = Company.getCompanyWithEmployee(name: "") 

No utilice campos de tránsito en las consultas; sus valores no están definidos en el momento en que se ejecuta la consulta. No se producirá ningún error, pero el resultado será incorrecto.

Establecer atributos (campos)


Probablemente haya notado que los atributos de entidad tienen varias opciones.
Con opcional, todo está claro por el nombre.

La opción de usar el tipo escalar apareció en swif. Objective-C no utiliza tipos escalares para los atributos, ya que no pueden ser nulos. Intentar asignar un valor escalar a un atributo a través de KVC causará un error de tiempo de ejecución. De esto queda claro por qué los tipos de atributos en Core Data no tienen una correspondencia estricta con los tipos de lenguaje. En proyectos rápidos y mixtos, se pueden usar atributos de tipo escalar.

Los atributos de tránsito son campos calculados que no se almacenan en la base de datos. Se pueden usar para el cifrado. Estos atributos reciben valores a través de un descriptor de acceso anulado, o mediante la asignación de primitivas según sea necesario (por ejemplo, willSave y awakeFromFetch anulados).

Accesores de atributos:


Si no necesita utilizar campos calculados, por ejemplo, para hacer cifrado u otra cosa, no tiene que pensar en qué son los accesorios de atributo. Mientras tanto, las operaciones de obtención y asignación de valores a los atributos no son "atómicas". Para entender lo que quiero decir, vea el código a continuación:

 //  let name = company.name //  company.willAccessValue(forKey: "name") let name = company.primitiveValue(forKey: "name") company.didAccessValue(forKey: "name") //  company.name = " " //  company.willChangeValue(forKey: "name") company.setPrimitiveValue(" ", forKey: "name") company.didChangeValue(forKey: "name") 

Use primitivas en eventos NSManagedObject en lugar de la asignación habitual para evitar bucles. Un ejemplo:

 override func willSave() {   let nameP = encrypt(field: primitiveValue(forKey: "name"), password: password)   setPrimitiveValue(nameP, forKey: "nameC")   super.willSave() }  override func awakeFromFetch() {   let nameP = decrypt(field: primitiveValue(forKey: "nameC"), password: password)   setPrimitiveValue(nameP, forKey: "name")   super.awakeFromFetch() } 

Si de repente en algún momento tiene que atornillar la función awakeFromFetch en un proyecto, se sorprenderá de que funcione de manera extraña, pero de hecho no se llama en absoluto cuando ejecuta la solicitud. Esto se debe al hecho de que Core Data tiene un mecanismo de almacenamiento en caché muy inteligente, y si la selección ya está en la memoria (por ejemplo, porque acaba de llenar esta tabla), no se llamará al método. Sin embargo, mis experimentos mostraron que, en términos de valores calculados, puede confiar con seguridad en el uso de awakeFromFetch, como dice la documentación de Apple. Si para la prueba y el desarrollo necesita forzar awakeFromFetch, agregue managedObjectContext.refreshAllObjects () antes de la solicitud.

Eso es todo.

Gracias a todos los que leyeron hasta el final.

Source: https://habr.com/ru/post/es436510/


All Articles