gpt4 book ai didi

swift - 具有通用属性的结构符合 Swift 中的 Encodable

转载 作者:行者123 更新时间:2023-12-03 21:16:41 29 4
gpt4 key购买 nike

我一直在寻找一种在结构中具有通用属性的方法,该属性在运行时定义类型,例如:

struct Dog {
let id: String
let value: ??
}

一个可能有用的简单用例是在构建 json 对象时。 node 可以是 intstringbool 、数组等,但除了可以更改的类型之外,对象 node 保持不变。

经过一番思考并使用 protocols 失败(得到通常的 protocol 'X' can only be used as a generic constraint because it has Self or associated type requirements 错误),我想出了 2 个不同的解决方案,#0 使用 type erasure 和 #1 使用 type-erasuregenerics

#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}]

即使结果相同,我坚信如果考虑更多类型,方法#1 是更多的 scalable,但是对于添加的每种类型,仍然需要在 2 个不同的区域进行更改。

我确信有更好的方法来实现这一点,但还没有找到它。很高兴听到有关它的任何想法或建议。

编辑 #0 2020/02/08:可选值

使用 Rob 的最佳答案,我现在尝试允许 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 类型,我正在寻找一种使用 Rob 的答案的可能性:
[{"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),
]

最佳答案

如果您所描述的确实是您想要的,则无需任何这些类型的橡皮擦即可完成。你所需要的只是一个闭包。 (但这假设 Dog 确实只存在于编码,正如您所描述的那样,除此之外没有任何东西需要 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)
}
}

由于 value仅在 valueEncoder 内部使用,世界其他地方不需要知道它的类型(狗甚至不需要知道它的类型)。这就是类型删除的全部内容。它不需要制作额外的包装类型或通用结构。

如果你想保留 DogString 这样的类型和 DogInt ,您也可以通过添加协议(protocol)来做到这一点:
protocol Dog: Encodable {
associatedtype Value: Encodable
var id: String { get }
var value: Value { get }
}

然后制作一个 DogEncoder 来处理编码(与上面相同,除了一个新的 init 方法):
struct DogEncoder: Encodable {
let valueEncoder: (Encoder) throws -> Void

init<D: Dog>(_ dog: D) {
self.valueEncoder = {
var container = $0.container(keyedBy: CodingKeys.self)
try container.encode(dog.id, forKey: .id)
try container.encode(dog.value, forKey: .value)
}
}

enum CodingKeys: String, CodingKey {
case id, value
}

func encode(to encoder: Encoder) throws {
try valueEncoder(encoder)
}
}

几种狗:
struct DogString: Dog {
let id: String
let value: String
}

struct DogInt: Dog {
let id: String
let value: Int
}

将它们放入编码器数组中:
let dogs = [
DogEncoder(DogString(id: "123", value: "pop")),
DogEncoder(DogInt(id: "123", value: 123)),
]

let data = try JSONEncoder().encode(dogs)

关于swift - 具有通用属性的结构符合 Swift 中的 Encodable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60096473/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com