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