49059


swift初心者です。

[code]

    let foo: Encodable
    
[/code]

などの`Encodable`の型を持つ変数を`JSONEncoder`でエンコードしたいです。  
調べたところ、<https://forums.swift.org/t/how-to-encode-objects-of-unknown-
type/12253>  
のサイトより、

[code]

    import Foundation
    
    extension Encodable {
        fileprivate func encode(to container: inout SingleValueEncodingContainer) throws {
            try container.encode(self)
        }
    }
    
    struct AnyEncodable1 : Encodable {
        var value: Encodable
        init(_ value: Encodable) {
            self.value = value
        }
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try value.encode(to: &container)
        }
    }
    
    struct MyThing : Encodable {
        let myURL = AnyEncodable1(URL(string: "http://swift.org")!)
    }
    
    let url = URL(string: "http://swift.org")
    let encoder = JSONEncoder()
    var data = try encoder.encode(MyThing())
    
[/code]

とすると`Encodable`の変数もエンコードできることがわかりました。

しかし、このコードだと`Encodable`を`AnyEncodable`1つにつき1個しか保存できません。

そこで、

[code]

    extension Encodable {
        fileprivate func encode<K>(to container: inout KeyedEncodingContainer<K>, for key: CodingKey {
            try container.encode(self, forKey: key) 
            // error: Cannot invoke 'encode' with an argument list of type '(Self, forKey: CodingKey)'
        }
    }
    
    struct AnyEncodable1 : Encodable {
        var value: Encodable
        init(_ value: Encodable) {
            self.value = value
        }
        enum CodingKeys: String, CodingKey {
            case value
        }
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try value.encode(to: &container, for: .value)
        }
    }
    
    struct MyThing : Encodable {
        let myURL = AnyEncodable1(URL(string: "http://swift.org")!)
    }
    
    let url = URL(string:  "http://swift.org")
    let encoder = JSONEncoder()
    var data = try encoder.encode(MyThing())
    
[/code]

というように`SingleValueEncodingContainer`を`KeyedEncodingContainer`に入れ替えました。しかし、入れ替えると先程までは出ていなかったエラーが出ました。  
`SingleValueEncodingContainer`と`KeyedEncodingContainer`のencodeメソッドの宣言はそれぞれ

[code]

    // SingleValueEncodingContainer
    mutating func encode<T>(_ value: T) throws where T : Encodable
    // KeyedEncodingContainer
    mutating func encode<T>(_ value: T, forKey key: KeyedEncodingContainer.Key) throws where T : Encodable
    
[/code]

と`KeyedEncodingContainer`にはキーが必要だということ以外は変わりません。  
しかし、なぜ`KeyedEncodingContainer`ではエラーが出るのでしょうか。  
ちなみに`UnkeyedEncodingContainer`に入れ替えるとエラーは出ませんでした。  
なぜこのようになるか理由を教えていただけると幸いです。  
よろしくお願い致します。  
swift4、xcode9.2です。


CC BY-SA 4.0
2018-10-08T10:42:24.917
49057
2018-10-08T13:54:40.380
2018-10-08T11:18:45.773
19110
30216
question
swift ios swift4
SingleValueEncodingContainerとKeyedEncodingContainerでEncodableを型に持つ変数をエンコードする時の挙動の違いについて(KeyedEncodingContainerだとエラーが出る)


メソッドヘッダの宣言が少し間違えています。(`) throws`が抜けてまずがそれは置いといて。)

* * *

まずは、`KeyedEncodingContainer.encode(_:forKey:)`の宣言をよくご覧ください。

[code]

    // KeyedEncodingContainer
    mutating func encode<T>(_ value: T, forKey key: KeyedEncodingContainer.Key) throws where T : Encodable
    
[/code]

`forKey`パラメータの型は任意の`CodingKey`型ではなく、`KeyedEncodingContainer.Key`型、つまりそのコンテナの`Key`型でないといけません。

その定義はこうなっています。

[KeyedEncodingContainer.Key](https://developer.apple.com/documentation/swift/keyedencodingcontainer/key)

> ## Declaration
[code]

>     typealias KeyedEncodingContainer<K>.Key = K
>  
[/code]

となっており、要は`KeyedEncodingContainer`のジェネリック型パラメータ`K`と同じ型を表しています。

* * *

あなたのメソッド宣言を見るとこうなっています。

[code]

    fileprivate func encode<K>(to container: inout KeyedEncodingContainer<K>, for key: CodingKey)
    
[/code]

この宣言ですと、パラメータ`key`は、`container`の型に関係なく、`CodingKey`に適合する型ならなんでも良いことになります。これでは先の`KeyedEncodingContainer.encode(_:forKey:)`メソッドを呼ぶことはできません。

以下のように`key`の型を書き換えてみてください。

[code]

    extension Encodable {
        fileprivate func encode<K>(to container: inout KeyedEncodingContainer<K>, for key: K) throws {
            try container.encode(self, forKey: key)
        }
    }
    
[/code]

これで、`key`は`encode(_:forKey:)`の宣言にある制約を満たすことになるので、その部分でのエラーは出なくなるはずです。


CC BY-SA 4.0
2018-10-08T13:54:40.380
49059
2018-10-08T13:54:40.380
13972
49057
answer