私は、構造体で、型が実行時に次のように定義される汎用プロパティを持つ方法を探していました。
struct Dog {
let id: String
let value: ??
}
これが役立つ単純な使用例は、json
オブジェクトを構築する場合です。Anode
は 、 、 、配列などにすることができますがint
、string
変更bool
可能な型を除けば、オブジェクトnode
は同じままです。
少し考えて使用に失敗した後protocols
(通常のエラーが発生しました)、2 つの異なる解決策をprotocol 'X' can only be used as a generic constraint because it has Self or associated type requirements
思いつきましtype erasure
た。type-erasure
generics
#0 (タイプ消去)
struct AnyDog: Encodable {
enum ValueType: Encodable {
case int(Int)
case string(String)
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
let id: String
let value: ValueType
init(_ dog: DogString) {
self.id = dog.id
self.value = .string(dog.value)
}
init(_ dog: DogInt) {
self.id = dog.id
self.value = .int(dog.value)
}
}
struct DogString: Encodable{
let id: String
let value: String
var toAny: AnyDog {
return AnyDog(self)
}
}
struct DogInt: Encodable {
let id: String
let value: Int
var toAny: AnyDog {
return AnyDog(self)
}
}
let dogs: [AnyDog] = [
DogString(id: "123", value: "pop").toAny,
DogInt(id: "123", value: 123).toAny,
]
do {
let data = try JSONEncoder().encode(dogs)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
}
#1 (型消去 + ジェネリック)
struct AnyDog: Encodable {
enum ValueType: Encodable {
case int(Int)
case string(String)
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
let id: String
let value: ValueType
}
struct Dog<T: Encodable>: Encodable{
let id: String
let value: T
var toAny: AnyDog {
switch T.self {
case is String.Type:
return AnyDog(id: id, value: .string(value as! String))
case is Int.Type:
return AnyDog(id: id, value: .int(value as! Int))
default:
preconditionFailure("Invalid Type")
}
}
}
let dogs: [AnyDog] = [
Dog<String>(id: "123", value: "pop").toAny ,
Dog<Int>(id: "123", value: 123).toAny,
]
do {
let data = try JSONEncoder().encode(dogs)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
}
どちらのアプローチでも適切な結果が得られます。
[{"id":"123","value":"pop"},{"id":"123","value":123}]
scalable
結果が同じであったとしても、より多くのタイプを考慮に入れれば、アプローチ #1 が有効であると強く信じていますが、追加されたタイプごとに 2 つの異なる領域で変更を加える必要があります。
これを達成するためのより良い方法があると確信していますが、まだ見つけることができませんでした。それについての考えや提案を聞いてうれしいです。
編集 #0 2020/02/08: オプション値
ロブのすばらしい答えを使用して、次のvalue
ようにオプションにできるようにしようとしています。
struct Dog: Encodable {
// This is the key to the solution: bury the type of value inside a closure
let valueEncoder: (Encoder) throws -> Void
init<T: Encodable>(id: String, value: T?) {
self.valueEncoder = {
var container = $0.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(value, forKey: .value)
}
}
enum CodingKeys: String, CodingKey {
case id, value
}
func encode(to encoder: Encoder) throws {
try valueEncoder(encoder)
}
}
let dogs = [
Dog(id: "123", value: 123),
Dog(id: "456", value: nil),
]
do {
let data = try JSONEncoder().encode(dogs)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
}
この時点で、T
これ以上推論できず、次のエラーがスローされます。
generic parameter 'T' could not be inferred
Optional
タイプが に与えられた場合、次の結果を与えるロブの答えを使用する可能性を探していますvalue
:
[{"id":"123","value":123},{"id":"456","value":null}]
編集 #1 2020/02/08: 解決策
value
さて、私は値を与えることに集中していたので、推論エラーの原因となる型がnil
ないことに気づきませんでした。nil
オプションのタイプを指定すると、機能します。
let optString: String? = nil
let dogs = [
Dog(id: "123", value: 123),
Dog(id: "456", value: optString),
]