Utilisation du décodable dans Swift 4 avec héritage

si l'usage de l'héritage de classe brise la Décodabilité de classe. Par exemple, le code suivant

class Server : Codable {
    var id : Int?
}

class Development : Server {
    var name : String?
    var userId : Int?
}

var json = "{"id" : 1,"name" : "Large Building Development"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil") here

sortie:

1
name is nil

maintenant si j'inverse ceci, le nom décode mais l'id ne décode pas.

class Server {
    var id : Int?
}

class Development : Server, Codable {
    var name : String?
    var userId : Int?
}

var json = "{"id" : 1,"name" : "Large Building Development"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil")

sortie:

id is nil
Large Building Development

et vous ne pouvez pas exprimer Codable dans les deux classes.

39
demandé sur Guilherme 2017-06-14 23:13:12

4 réponses

je crois que dans le cas de l'héritage, vous devez implémenter Coding vous-même. C'est, vous devez spécifier CodingKeys et de mettre en œuvre init(from:) et encode(to:) dans la superclasse et la sous-classe. Par le WWDC video (environ 49:28, sur la photo ci-dessous), vous devez appeler super avec le super encoder/décodeur.

WWDC 2017 Session 212 Screenshot at 49:28 (Source Code)

required init(from decoder: Decoder) throws {

  // Get our container for this subclass' coding keys
  let container = try decoder.container(keyedBy: CodingKeys.self)
  myVar = try container.decode(MyType.self, forKey: .myVar)
  // otherVar = ...

  // Get superDecoder for superclass and call super.init(from:) with it
  let superDecoder = try container.superDecoder()
  try super.init(from: superDecoder)

}

La vidéo semble s'arrêter de montrer l'encodage côté (mais c'est container.superEncoder() pour les encode(to:) côté) mais il fonctionne de la même manière dans votre encode(to:) mise en œuvre. Je peux confirmer cela fonctionne dans ce cas simple (voir le code de terrain de jeu ci-dessous).

je suis toujours aux prises avec un comportement étrange moi-même avec un modèle beaucoup plus complexe que je suis en train de convertir de NSCoding, qui a beaucoup de nouveaux types imbriqués (y compris struct et enum) qui expose cet inattendu nil comportement et "ne devrait pas être". Juste être conscient qu'il peut y avoir des cas de bord qui impliquent des types imbriqués.

Edit: les types imbriqués semblent bien fonctionner dans mon terrain de jeu d'essai; je soupçonne maintenant quelque chose de mal à l'auto-référencement des classes (pensez aux enfants de noeuds d'arbre) avec une collection de lui-même qui contient également des instances de cette classe' diverses sous-classes. Un test d'une classe simple d'auto-référencement décode bien (c'est-à-dire, pas de sous-classes) donc je me concentre maintenant sur la raison pour laquelle le cas des sous-classes échoue.

Mettre À Jour Le 25 Juin De 17: j'ai fini je dépose un micro avec Apple à ce sujet. rdar: / / 32911973-malheureusement un cycle d'encodage / décodage d'un tableau de Superclass qui contient Subclass: Superclass éléments entraîne tous les éléments de la matrice de décodage Superclass (la sous-classe' init(from:) n'est jamais appelé, ce qui entraîne une perte de données ou pire).

//: Fully-Implemented Inheritance

class FullSuper: Codable {

    var id: UUID?

    init() {}

    private enum CodingKeys: String, CodingKey { case id }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UUID.self, forKey: .id)

    }

    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)

    }

}

class FullSub: FullSuper {

    var string: String?
    private enum CodingKeys: String, CodingKey { case string }

    override init() { super.init() }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        let superdecoder = try container.superDecoder()
        try super.init(from: superdecoder)

        string = try container.decode(String.self, forKey: .string)

    }

    override func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(string, forKey: .string)

        let superdecoder = container.superEncoder()
        try super.encode(to: superdecoder)

    }
}

let fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = "FullSub"

let fullEncoder = PropertyListEncoder()
let fullData = try fullEncoder.encode(fullSub)

let fullDecoder = PropertyListDecoder()
let fullSubDecoded: FullSub = try fullDecoder.decode(FullSub.self, from: fullData)

les propriétés de la super-et de la sous-classe sont restaurées dans fullSubDecoded.

38
répondu Joshua Nozzi 2018-06-07 11:33:16

J'ai trouvé ce lien - allez à la section héritage

override func encode(to encoder: Encoder) throws {
    try super.encode(to: encoder)
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(employeeID, forKey: .employeeID)
}

Pour le Décodage j'ai fait ceci:

 required init(from decoder: Decoder) throws {

    try super.init(from: decoder)

    let values = try decoder.container(keyedBy: CodingKeys.self)
    total = try values.decode(Int.self, forKey: .total)
  }

private enum CodingKeys: String, CodingKey
{
    case total

}
4
répondu user2704776 2018-01-30 13:52:00

j'ai pu le faire fonctionner en rendant ma classe de base et mes sous-classes conformes à Decodable au lieu de Codable. Si j'ai utilisé Codable il s'écraserait de façon étrange, comme obtenir un EXC_BAD_ACCESS lors de l'accès à un champ de la sous-classe, pourtant le débogueur pouvait afficher toutes les valeurs de la sous-classe sans problème.

de plus, en passant le superdécodeur à la classe de base dans super.init() ne fonctionne pas. Je viens de passer le décodeur de la sous-classe de la classe de base.

4
répondu ShackBurger 2018-03-04 13:33:54

Que Diriez-vous d'utiliser la façon suivante?

protocol Parent: Codable {
    var inheritedProp: Int? {get set}
}

struct Child: Parent {
    var inheritedProp: Int?
    var title: String?

    enum CodingKeys: String, CodingKey {
        case inheritedProp = "inherited_prop"
        case title = "short_title"
    }
}

informations supplémentaires sur la composition:http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/

4
répondu Nav 2018-06-08 08:39:58