

SwiftのCodableプロトコルに準拠したクラスとそのサブクラスを作成し、それらが混在した配列をPropertyListで書き出し・読み込みを行いたいのですが、クラスの判別ができないようです。

サンプルとしてAnimalクラスを作成し、サブクラスとしてDogを追加しています。本来であればCatやMonkeyなど複数のサブクラスも作成し、

[code]

    var animals: [Animal]
    
    animals.append(Dog())
    animals.append(Cat())
    
[/code]

などと追加してanimasをPropertyListとして保存するのが目的です。

エンコードは

[code]

    let data = try PropertyListEncoder().encode(animals)
    
[/code]

で、デコードは

[code]

    let animals2 = try PropertyListDecoder().decode([Animal].self, from: data)
    
[/code]

としています。[Animal].selfとしているせいなのかanimals2にはDogやCatではなくAnimalが復元されてしまいます。

何か書き方を間違えているのでしょうか？

Playgroundでテストできるサンプルです。

[code]

    import UIKit
    
    
    class Animal: Codable {
        var legCount: Int
    
        init() {
            legCount = 4
        }
    }
    
    class Dog: Animal {
        var name: String = "(NO NAME)"
    
        override init() {
            super.init()
        }
    
        private enum CodingKeys: String, CodingKey {
            case name
        }
    
        required init(from decoder: Decoder) throws {
            try super.init(from: decoder)
            let container = try decoder.container(keyedBy: CodingKeys.self)
            name = try container.decode(String.self, forKey: .name)
        }
    
        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)
        }
    }
    
    let dog = Dog()
    dog.name = "John"
    
    let animals: [Animal] = [dog]
    
    let data = try PropertyListEncoder().encode(animals)
    let animals2 = try PropertyListDecoder().decode([Animal].self, from: data)
    let a = animals2.first! as? Dog
    print(a?.name)
    
[/code]

本来であれば最後にJohnと表示されるべきだと思うのですが、aがnilとなってしまいます。

よろしくお願いいたします。


CC BY-SA 4.0
2018-11-21T08:45:52.153
50506
2020-09-12T08:02:15.210
9033
question
swift ios macos
Codableプロトコルを利用してPropertyListへの書き出しを行う際のクラスの指定方法

[code]

    let animals2 = try PropertyListDecoder().decode([Animal].self, from: data)
    
[/code]

の行は、`Array<Animal>`へデコードしなさい。と言う意味で、`Animal`のサブクラスの配列へデコードしなさい。ではないですよね？  
`Dog`,
`Cat`などのクラス（のインスタンス）は、継承元がAnimalであることは解っても、Animalクラスは自身を継承しているクラスが何であるか？は不明です。

更に、`decode([Animal].self`としていますので、`init(from
decoder)`が呼ばれるのも、それぞれの継承先クラスの`init(from decoder)`ではなく、`Animal`クラスの`init(from
decoder)`です。  
なので、配列`animals2`は、`Dog`等の継承先クラスの配列ではなく、`Animal`クラスのインスタンスの配列となり、`Dog`クラスのインスタンスではなくなってしまうので、`name`プロパティなんて存在しないよ。  
と、なってしまうのです。

ですので、解決作としては、継承の最終段クラスが単一クラスであり、そのクラスのインスタンスは`dog`属性や `cat`属性を持つ様に

[code]

    enum Species {
        case dog
        case cat
          // 以下略
    }
    
    class Animal: Codable {
        var species: Species
        var name: String
    
        init(_ species: Species, name: String = "(NO NAME)") {
            self.species = species
            self.name = name
        }
    }
    
[/code]

等とし、

[code]

    let dog = Animal(.dog)
    
[/code]

で、としてそれぞれの種のインスタンスを生成するのがプロパティリストエンコード／デコードするためには必要だと思います。


CC BY-SA 4.0
2018-11-22T05:04:50.303
50531
2018-11-22T05:22:13.343
2018-11-22T05:22:13.343
14745
14745
50506
answer