Como usar o Swift 4 Codable in Core Data?




core-data swift4 (3)

Swift 4.2:

Após a solução de casademora,

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

deveria estar

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

Isso evita erros que o Xcode reconhece falsamente como problemas de fatia de matriz.

Edit: Use opcionalmente implicitamente desembrulhados para remover a necessidade de forçar o unwrap .context toda vez que ele estiver sendo usado.

Codable parece um recurso muito interessante. Mas eu me pergunto como podemos usá-lo no Core Data? Em particular, é possível codificar / decodificar diretamente um JSON de / para um NSManagedObject?

Eu tentei um exemplo muito simples:

e defini o próprio Foo :

import CoreData

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

Mas ao usá-lo assim:

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)

O compilador falhou com este erro:

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

e o arquivo de destino era o arquivo que definia Foo

Eu acho que provavelmente fiz errado, já que eu nem passei um NSManagedObjectContext , mas não tenho idéia de onde colocá-lo.

O Core Data suporta Codable ?


Como uma alternativa para aqueles que gostariam de fazer uso da abordagem moderna do XCode para geração de arquivos NSManagedObject , eu criei uma classe DecoderWrapper para expor um objeto Decoder que eu uso em meu objeto que está em conformidade com um 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)
    }
}

Isso provavelmente só é útil para modelos sem nenhum atributo não opcional, mas resolve meu problema de querer usar o Decodable mas também gerencia relacionamentos e persistência com o Core Data sem ter que criar manualmente todas as minhas classes / propriedades.


Você pode usar a interface Codable com objetos CoreData para codificar e decodificar dados, no entanto, não é tão automática quanto quando usada com objetos simples e antigos. Veja como você pode implementar a decodificação JSON diretamente com objetos Core Data:

Primeiro, você faz seu objeto implementar o Codable. Essa interface deve ser definida no objeto e não em uma extensão. Você também pode definir suas chaves de codificação nessa classe.

class MyManagedObject: NSManagedObject, Codable {
    @NSManaged var property: String?

    enum CodingKeys: String, CodingKey {
       case property = "json_key"
    }
}

Em seguida, você pode definir o método init. Isso também deve ser definido no método de classe porque o método init é exigido pelo protocolo Decodable.

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

No entanto, o inicializador adequado para uso com objetos gerenciados é:

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

Então, o segredo aqui é usar o dicionário userInfo para passar o objeto de contexto apropriado para o inicializador. Para fazer isso, você precisará estender a estrutura CodingUserInfoKey com uma nova chave:

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

Agora, você pode apenas como o decodificador para o 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)
}

Agora, quando você configurar a decodificação para Objetos Gerenciados, precisará transmitir o objeto de contexto adequado:

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 dados, você precisará fazer algo semelhante usando a função de protocolo de codificação .





codable