web-dev-qa-db-ja.com

継承を伴うSwift 4でのDecodableの使用

クラスの継承を使用すると、クラスのDecodabilityが壊れます。たとえば、次のコード

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

出力は次のとおりです。

1
name is nil

これを逆にすると、名前はデコードされますが、idはデコードされません。

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")

出力は次のとおりです。

id is nil
Large Building Development

また、両方のクラスでCodableを表現することはできません。

56
Kevin McQuown

継承の場合は、Codingを自分で実装する必要があると思います。つまり、CodingKeysを指定し、スーパークラスとサブクラスの両方でinit(from:)encode(to:)を実装する必要があります。 WWDCビデオ (49:28前後、下図)ごとに、スーパーエンコーダー/デコーダーでsuperを呼び出す必要があります。

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)

}

このビデオはエンコード側の表示が止まっているように見えますが(container.superEncoder()側のencode(to:)です)、encode(to:)実装でもほぼ同じように機能します。この単純なケースでこれが機能することを確認できます(以下のプレイグラウンドコードを参照)。

私はまだ、この予想外のNSCoding動作と「すべきではない」を示している多くの新しくネストされたタイプ(structenumを含む)を持っているnilから変換しているはるかに複雑なモデルで、奇妙な動作に苦労しています。 。入れ子になった型が関係するEdgeケースがある場合があることに注意してください。

編集:ネストされたタイプは、私のテストプレイグラウンドでうまく機能するようです。私は、自己参照クラス(ツリーノードの子と考えてください)に何か問題があるのではないかと疑っています。そのクラスのさまざまなサブクラスのインスタンスも含まれる自身のコレクションです。単純な自己参照クラスのテストでは問題なく(つまり、サブクラスはありません)デコードされるため、現在はサブクラスのケースが失敗する理由に重点を置いています。

17年6月25日更新:これについてAppleでバグを報告することになりました。 rdar:// 32911973-残念ながら、Subclass: Superclass要素を含むSuperclass配列のエンコード/デコードサイクルにより、配列内のすべての要素がSuperclassとしてデコードされます(サブクラスinit(from:)は呼び出されません。データの損失またはそれ以上の悪化につながります)。

//: 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 superencoder = container.superEncoder()
        try super.encode(to: superencoder)

    }
}

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)

スーパークラスプロパティとサブクラスプロパティの両方がfullSubDecodedに復元されます。

62
Joshua Nozzi

このリンクを見つける-継承セクションに移動

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)
}

デコードのために私はこれをしました:

 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

}
10
devjme

基本クラスとサブクラスをDecodableではなくCodableに準拠させることで、機能させることができました。 Codableを使用すると、サブクラスのフィールドにアクセスするときにEXC_BAD_ACCESSを取得するなど、奇妙な方法でクラッシュしますが、デバッガーはすべてのサブクラスの値を問題なく表示できます。

また、superDecoderをsuper.init()の基本クラスに渡すこともできませんでした。サブクラスから基本クラスにデコーダーを渡しました。

4
ShackBurger

次の方法を使用してはどうですか?

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"
    }
}

構成に関する追加情報: http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/

3
Nav

ここにライブラリがあります TypePreservingCodingAdapter これを行うだけです(CocoapodsまたはSwiftPackageManagerでインストールできます)。

以下のコードは、Swift 4.2でコンパイルおよび正常に動作します。残念ながら、サブクラスごとに、プロパティのエンコードとデコードを独自に実装する必要があります。

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
2
Igor Muzyka