72746


下記のような自動生成される2つの struct があるとします。

[code]

    public struct Hoge: Codable { /*中身は略*/ }
    public struct Foo: Codable { /*中身は略*/ }
    
[/code]

他にも自動生成されるstructがあるのですが、その中から上記2つは特別な存在（今回はUserDefaultsに保存する対象）ということを示すために、protocolでそれを明記しようとしました。上記
struct は自動生成されるので、自動生成されたファイルには手を加えたくないので、自動生成されたファイルとは別のところに下記コードを足しました。

[code]

    protocol Savedable: Codable {}
    extension Hoge: Savedable {} // 単なるマーキングとしてのprotocol付与なので、中身は本当に実装なし
    extension Foo: Savedable {} // 単なるマーキングとしてのprotocol付与なので、中身は本当に実装なし
    
[/code]

UserDefaultsに上記2つの struct
を保存することには変わりないのですが、上記2つを更に別のオブジェクトでラップして、そのオブジェクトをArrayにしてUserDefaultsに保存したい事情がありました。（つまり1つの配列に上記2つのインスタンスが入ってくようにしたかった）

[code]

    struct SavedableHolder: Codable {
        var tag: String
        var savedable: Savedable
    }
    
[/code]

そこで上記のような struct を作りこれを `Array<SavedableHolder>`に保存させようとしました。 tag
をなぜ用意したかというと、この tag には
"Hoge"や"Foo"という文字列を入れて、UserDefaultsから取り出すときにこの文字列をヒントにして、元の型に戻そうとしているためです。（UserDefaultsに保存するときはAnyやDataになってしまうため）

と、ここまでやや複雑な事情を書いてきましたが、とにかく起こっている問題としては、

上記コードで

> Type 'SavedableHolder' does not conform to protocol 'Decodable'

というエラーが出てしまっており、どう書き直したらいいのか、わかっておらず、なにか書き直せる方法があるのか知りたいです。

（ちなみに、うまい書き方が思いつかなかったので、やりたいことは少しずれてしまうのですが、`Array<Hoge>`と`Array<Foo>`の配列を2つ作ってなんとか開発は進めています。ひとつの配列になったほうが、ベターな設計ではありますが、マストではありません。とはいえ、技術的になぜ上記書き方でエラーになっているのか？いい書き方はないのか？気になっております。）

たとえば  
<https://teratail.com/questions/115556>  
の回答には似たような入れ子構造がありますが、なぜ私のコードではコンパイルエラーになってしまうのかわかりません。


CC BY-SA 4.0
2020-12-18T08:12:20.517
72737
2020-12-18T11:08:55.050
2020-12-18T08:38:06.367
9008
9008
question
swift
自動生成されるCodableな struct にさらにプロトコルを付与して Codableなstructの中に入れたい


一番の問題はSwiftのプロトコルに関する次の問題ですね。

### プロトコルPを型として使った場合、型PはプロトコルPには適合しない

(`Error`プロトコルだけが唯一の例外で、`Error`型は`Error`プロトコルに適合します。)

ご質問に挙げられた例の場合、`Savedable`と言うプロトコルは`Codable`を継承しているんですが、型として使った場合の`Savedable`型は`Codable`にも`Savedable`にも適合しません。

従って、`SavedableHolder`には`Codable`ではないプロパティが存在するため、`:
Codable`と書くだけでSwiftコンパイラが自動的に`SavedableHolder`を`Codable`に適合してくれるようにすると言うことも起こりません。

* * *

どうしても、`SavedableHolder`のような型を`Codable`に適合させたければ、`Decodable`に必要な`init(from:)`と`Encodable`に必要な`encode(to:)`を自前で実装してやらないといけません。

[code]

    struct SavedableHolder: Codable {
        var tag: String
        var savedable: Savedable
    }
    
    extension SavedableHolder {
        
        enum CodingKeys: CodingKey {
            case tag, savedable
        }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.tag = try container.decode(String.self, forKey: .tag)
            switch self.tag {
            case "Hoge":
                self.savedable = try container.decode(Hoge.self, forKey: .savedable)
            default:
                self.savedable = try container.decode(Foo.self, forKey: .savedable)
            }
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(tag, forKey: .tag)
            if let hoge = savedable as? Hoge {
                try container.encode(hoge, forKey: .savedable)
            } else if let foo = savedable as? Foo {
                try container.encode(foo, forKey: .savedable)
            }
        }
    }
    
[/code]

もちろん、`savedable`に格納する可能性のある型が増えていけば、`init(from:)`も`encode(to:)`も書き直さなければいけなくなります。

この辺りは、静的な型システム上に、コンパイラが一部のメソッドやイニシャライザを自動合成するというSwiftの`Codable`の仕組みの限界と言えるでしょう。`SavedableHolder`の構造を見直した方が良いかもしれません。


CC BY-SA 4.0
2020-12-18T11:08:55.050
72746
2020-12-18T11:08:55.050
13972
72737
answer