swift xcode9-beta core-data swift4 xcode9 codable

¿Cómo usar swift 4 Codable en Core Data?



xcode9-beta core-data (4)

Codificable parece una característica muy emocionante. Pero me pregunto cómo podemos usarlo en Core Data. En particular, ¿es posible codificar / decodificar directamente un JSON desde / a un NSManagedObject?

Intenté un ejemplo muy simple:

y yo mismo Foo :

import CoreData @objc(Foo) public class Foo: NSManagedObject, Codable {}

Pero cuando lo usas así:

let json = """ { "name": "foo", "bars": [{ "name": "bar1", }], [{ "name": "bar2" }] } """.data(using: .utf8)! let decoder = JSONDecoder() let foo = try! decoder.decode(Foo.self, from: json) print(foo)

El compilador falló con este error:

super.init isn''t called on all paths before returning from initializer

y el archivo de destino era el archivo que definía a Foo

Supongo que probablemente lo hice mal, ya que ni siquiera NSManagedObjectContext un NSManagedObjectContext , pero no tengo idea de dónde pegarlo.

¿Core Data admite Codable ?


Swift 4.2:

Siguiendo la solución de casademora,

guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }

debiera ser

guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() } guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() } .

Esto evita errores que Xcode reconoce falsamente como problemas de corte de matriz.

Editar: Use opciones implícitamente desenvueltas para eliminar la necesidad de forzar el desenvolvimiento de .context cada vez que se usa.


Como alternativa para aquellos que NSManagedObject utilizar el enfoque moderno de XCode para la generación de archivos NSManagedObject , he creado una clase DecoderWrapper para exponer un objeto Decoder que luego uso dentro de mi objeto que se ajusta a un protocolo JSONDecoding :

class DecoderWrapper: Decodable { let decoder:Decoder required init(from decoder:Decoder) throws { self.decoder = decoder } } protocol JSONDecoding { func decodeWith(_ decoder: Decoder) throws } extension JSONDecoding where Self:NSManagedObject { func decode(json:[String:Any]) throws { let data = try JSONSerialization.data(withJSONObject: json, options: []) let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data) try decodeWith(wrapper.decoder) } } extension MyCoreDataClass: JSONDecoding { enum CodingKeys: String, CodingKey { case name // For example } func decodeWith(_ decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(String.self, forKey: .name) } }

Esto probablemente solo sea útil para modelos sin ningún atributo no opcional, pero resuelve mi problema de querer usar Decodable pero también administrar relaciones y persistencia con Core Data sin tener que crear manualmente todas mis clases / propiedades.


CoreData es su propio marco de persistencia y, según su documentación exhaustiva, debe utilizar sus inicializadores designados y seguir una ruta bastante específica para crear y almacenar objetos con él.

Sin embargo, aún puede usar Codable con él de formas limitadas al igual que puede usar NSCoding .

Una forma es decodificar un objeto (o una estructura) con cualquiera de estos protocolos y transferir sus propiedades a una nueva instancia de NSManagedObject que haya creado según los documentos de Core Data.

Otra forma (que es muy común) es usar uno de los protocolos solo para un objeto no estándar que desea almacenar en las propiedades de un objeto administrado. Por "no estándar", me refiero a todo lo que no se ajusta a los tipos de atributos estándar de Core Data como se especifica en su modelo. Por ejemplo, NSColor no se puede almacenar directamente como una propiedad de Objeto administrado, ya que no es uno de los tipos de atributos básicos que admite el CD. En su lugar, puede usar NSKeyedArchiver para serializar el color en una instancia de NSData y almacenarlo como una propiedad de datos en el objeto administrado. Invierta este proceso con NSKeyedUnarchiver . Eso es simplista y hay una manera mucho mejor de hacer esto con Core Data (ver Atributos transitorios ) pero ilustra mi punto.

También podría adoptar Encodable (uno de los dos protocolos que componen Codable , ¿puede adivinar el nombre del otro?) Para convertir una instancia de Objeto administrado directamente a JSON para compartir, pero tendría que especificar las claves de codificación y su propia costumbre encode implementación ya que el compilador no la sintetizará automáticamente con claves de codificación personalizadas. En este caso, desearía especificar solo las claves (propiedades) que desea incluir.

Espero que esto ayude.


Puede usar la interfaz Codificable con objetos CoreData para codificar y decodificar datos, sin embargo, no es tan automática como cuando se usa con objetos rápidos y simples. A continuación, le mostramos cómo puede implementar la decodificación JSON directamente con objetos de Core Data:

Primero, haces que tu objeto implemente Codificable. Esta interfaz debe estar definida en el objeto y no en una extensión. También puede definir sus claves de codificación en esta clase.

class MyManagedObject: NSManagedObject, Codable { @NSManaged var property: String? enum CodingKeys: String, CodingKey { case property = "json_key" } }

A continuación, puede definir el método init. Esto también debe definirse en el método de clase porque el protocolo Decodable requiere el método init.

required convenience init(from decoder: Decoder) throws { }

Sin embargo, el inicializador adecuado para usar con objetos administrados es:

NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)

Entonces, el secreto aquí es usar el diccionario userInfo para pasar el objeto de contexto apropiado al inicializador. Para hacer esto, necesitará extender la estructura CodingUserInfoKey con una nueva clave:

extension CodingUserInfoKey { static let context = CodingUserInfoKey(rawValue: "context") }

Ahora, puede simplemente como decodificador para el contexto:

required convenience init(from decoder: Decoder) throws { guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() } guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() } self.init(entity: entity, in: context) let container = decoder.container(keyedBy: CodingKeys.self) self.property = container.decodeIfPresent(String.self, forKey: .property) }

Ahora, cuando configure la decodificación para Objetos administrados, deberá pasar el objeto de contexto adecuado:

let data = //raw json data in Data object let context = persistentContainer.newBackgroundContext() let decoder = JSONDecoder() decoder.userInfo[.context] = context _ = try decoder.decode(MyManagedObject.self, from: data) //we''ll get the value from another context using a fetch request later... try context.save() //make sure to save your data once decoding is complete

Para codificar datos, deberá hacer algo similar con la función de protocolo de codificación .