【发布时间】:2020-04-09 09:50:36
【问题描述】:
如何解码/编码不同泛型类型的数组?
我有一个数据结构,它具有符合Connection 协议的属性,因此我使用泛型:
// Data structure which saves two objects, which conform to the Connection protocol
struct Configuration<F: Connection, T: Connection>: Codable {
var from: F
var to: T
private var id: String = UUID.init().uuidString
enum CodingKeys: String, CodingKey {
case from, to, id
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.from = try container.decode(F.self, forKey: .from)
self.to = try container.decode(T.self, forKey: .to)
self.id = try container.decode(String.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(from, forKey: .from)
try container.encode(to, forKey: .to)
try container.encode(id, forKey: .id)
}
}
protocol Connection: Codable {
var path: String { get set }
}
// Two implementations of the Connection protocol
struct SFTPConnection: Connection, Codable {
var path: String
var user: String
var sshKey: String
}
struct FTPConnection: Connection, Codable {
var path: String
var user: String
var password: String
}
当我知道F 和T 是什么类型的连接时,这很好用。但是我有一些情况,我想加载配置,不知道 F 和 T 是哪种类型。
public static func load<F: Connection, T: Connection>(for key: String) throws -> Configuration<F, T>? {
// Load from UserDefaults
guard let configurationData = defaults.object(forKey: key) as? Data else {
return nil
}
// Decode
guard let configuration = try? PropertyListDecoder().decode(Configuration<F, T>.self, from: configurationData) else {
return nil
}
return configuration
}
// OR
func loadAll<F:Connection, T: Connection>() -> [String: Configuration<F, T>]? {
return UserDefaults.standard.dictionaryRepresentation() as? [String: Configuration<F, T>]
}
在上述情况下,F 和 T 可以是符合 Connection 协议的任何未知类型。所以上面的函数是行不通的,因为我在调用函数时需要为F和T指定一个特定的类型,我不知道。
在第二个函数中,F 本身实际上可以是不同的类型。这就是困难的地方。我想我需要以某种方式将F 和T 的类型存储在用户默认值中,然后在decode 和encode 函数中使用它们(从而丢弃泛型)。但我不知道如何优雅地做到这一点。
我将不胜感激有关如何解决此问题的任何想法!
【问题讨论】:
-
短篇小说:你必须知道类型。你有多少种不同的类型?您可以将类型字符串与数据一起保存(例如作为字典),并在 switch 语句中在检索数据时将字符串映射到具体类型。
-
@vadian 我已经添加了一个答案,这是我能想到的最好的答案。但这不是很漂亮,因为有很多重复的代码。你知道如何缩短它吗?
-
如何使用Connection?按照您编写的方式,调用者必须知道配置的确切类型才能创建泛型。这没有多大意义。例如,什么调用代码可以访问
.password?该协议不包括该协议,因此 Configuration 无法使用它。我不明白为什么配置是通用的。感觉 Connection 需要更多方法。 -
对于这个特定问题更关键的是,连接实现的列表是否可能很小且是静态的(如这 2 个),还是可能存在您无法控制的任意数量的实现?两者都是可解的,但后者更复杂。
-
@RobNapier 是的,没错,调用者需要知道
Connection的类型,而这正是问题所在。使用泛型的方法来自另一个问题(请参阅stackoverflow.com/q/59353454/3272409),我最初的问题是,我无法将协议与Codable一起使用。现在我看到使用泛型是行不通的。最后,调用者应该收到一个Configuration对象,并且必须知道Connection的类型。所以现在我知道,我必须将类型存储在Configuration的某个变量中。有了这些信息,调用者就可以访问.password。
标签: swift generics encoding decode codable