L'utilisation de l'héritage de classe doit-elle annuler la décodabilité de la 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
la sortie est:
1
name is nil
Maintenant, si j'inverse cela, name décode mais id 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")
la sortie est:
id is nil
Large Building Development
Et vous ne pouvez pas exprimer Codable dans les deux classes.
Je crois qu'en cas d'héritage, vous devez implémenter vous-même Coding
. Autrement dit, vous devez spécifier CodingKeys
et implémenter init(from:)
et encode(to:)
à la fois dans la superclasse et la sous-classe. Selon la vidéo WWDC (vers 49:28, photo ci-dessous), vous devez appeler super avec le super codeur/décodeur.
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 ne pas montrer le côté encodage (mais c'est container.superEncoder()
pour le côté encode(to:)
), mais cela fonctionne de la même manière dans votre implémentation encode(to:)
. Je peux confirmer que 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 convertis à partir de NSCoding
, qui contient de nombreux types nouvellement imbriqués (dont struct
et enum
) qui présentent ce comportement inattendu nil
et "ne devraient pas être". . Sachez simplement qu'il peut y avoir des cas Edge impliquant des types imbriqués.
Edit: Les types imbriqués semblent bien fonctionner dans ma zone de test; Je soupçonne maintenant quelque chose qui ne va pas avec les classes auto-référencées (pensez aux enfants des noeuds d'arborescence) avec une collection d'elle-même qui contient également des instances des différentes sous-classes de cette classe. Un test d'une classe simple à auto-référencement décode parfaitement (c'est-à-dire sans sous-classes). Je concentre donc maintenant mes efforts sur les raisons de l'échec de la casse des sous-classes.
Mise à jour du 25 juin 17: J'ai fini par déposer un bogue avec Apple à ce sujet. rdar: // 32911973 - Malheureusement, un cycle d'encodage/décodage d'un tableau de Superclass
contenant des éléments Subclass: Superclass
entraîne le décodage de tous les éléments du tableau en tant que Superclass
(la sous-classe 'init(from:)
n'est jamais appelée, 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-classe et de la sous-classe sont restaurées dans fullSubDecoded
.
Found This Link - 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 décoder 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
}
Que diriez-vous d'utiliser la manière 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/
J'ai pu le faire fonctionner en rendant ma classe de base et mes sous-classes conformes à Decodable
au lieu de Codable
. Si j'utilisais Codable
, cela planterait de manière étrange, par exemple en obtenant un EXC_BAD_ACCESS
lors de l'accès à un champ de la sous-classe, mais le débogueur pourrait afficher toutes les valeurs de la sous-classe sans problème.
De plus, transmettre le superDécodeur à la classe de base dans super.init()
n'a pas fonctionné. Je viens de passer le décodeur de la sous-classe à la classe de base.
Voici une bibliothèque TypePreservingCodingAdapter pour faire exactement cela (peut être installé avec Cocoapods ou SwiftPackageManager).
Le code ci-dessous est compilé et fonctionne parfaitement avec Swift 4.2
. Malheureusement, pour chaque sous-classe, vous devrez implémenter vous-même le codage et le décodage.
import TypePreservingCodingAdapter
import Foundation
// redeclared your types with initializers
class Server: Codable {
var id: Int?
init(id: Int?) {
self.id = id
}
}
class Development: Server {
var name: String?
var userId: Int?
private enum CodingKeys: String, CodingKey {
case name
case userId
}
init(id: Int?, name: String?, userId: Int?) {
self.name = name
self.userId = userId
super.init(id: id)
}
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decodeIfPresent(String.self, forKey: .name)
userId = try container.decodeIfPresent(Int.self, forKey: .userId)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(userId, forKey: .userId)
}
}
// create and adapter
let adapter = TypePreservingCodingAdapter()
let encoder = JSONEncoder()
let decoder = JSONDecoder()
// inject it into encoder and decoder
encoder.userInfo[.typePreservingAdapter] = adapter
decoder.userInfo[.typePreservingAdapter] = adapter
// register your types with adapter
adapter.register(type: Server.self).register(type: Development.self)
let server = Server(id: 1)
let development = Development(id: 2, name: "dev", userId: 42)
let servers: [Server] = [server, development]
// wrap specific object with Wrap helper object
let data = try! encoder.encode(servers.map { Wrap(wrapped: $0) })
// decode object back and unwrap them force casting to a common ancestor type
let decodedServers = try! decoder.decode([Wrap].self, from: data).map { $0.wrapped as! Server }
// check that decoded object are of correct types
print(decodedServers.first is Server) // prints true
print(decodedServers.last is Development) // prints true