Swift-解码/编码具有不同类型的泛型数组 [英] Swift - Decode/encode an array of generics with different types
问题描述
如何解码/编码不同通用类型的数组?
How can I decode/encode an array of different generic types?
我有一个数据结构,该结构具有符合协议<$ c $的属性c> Connection ,因此我使用泛型:
I have a data structure, which has properties, that conform to a protocol Connection
, thus I use generics:
// 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
是哪种类型。
This works fine when I know of what type the connections F
and T
are. But I have cases, where I want to load a configuration, not knowing which type F
and T
are.
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
指定特定类型函数,我不知道。
In the above cases F
and T
could be of any unknown type, that conforms to the Connection
protocol. So the above functions wouldn't work, since I would need to specify a specific type for F
and T
when calling the function, which I don't know.
在第二个函数中,单独的 F
实际上可能是不同的类型。那就是困难所在。我想我还需要以某种方式在用户默认值中存储 F
和 T
的类型,然后在 decode
和 encode
函数(从而丢弃泛型)。但是我不知道我该如何优雅地做到这一点。
In the second function, F
alone could actually be of different types. That's where it gets difficult. I figured I need to somehow store the types of F
and T
in the User Defaults as well and then use them in the decode
and encode
function (thus discarding the generics). But I have no idea how I would elegantly do that.
对于解决这个问题的任何想法,我将不胜感激!
I would appreciate any ideas on how to solve this problem!
推荐答案
以下解决方案解决了我在使用泛型时遇到的所有问题,并且不了解 Connection
的特定类型。解决方案的关键是
The following solutions resolves all the issues, that I had with generics and not knowing the specific type of Connection
. The key to the solution was
- 将
Connection
实现的类型保存在实现本身和 - 使用
superEncoder
和superDecoder
来编码/解码从
和到
属性。
- saving the type of a
Connection
implementation in the implementation itself and - Using
superEncoder
andsuperDecoder
to encode/decode thefrom
andto
properties.
这是解决方案:
import Foundation
protocol Connection: Codable {
var type: ConnectionType { get }
var path: String { get set }
}
struct LocalConnection: Connection {
let type: ConnectionType = ConnectionType.local
var path: String
}
struct SFTPConnection : Connection {
let type: ConnectionType = ConnectionType.sftp
var path: String
var user: String
var sshKey: String
init(path: String, user: String, sshKey: String) {
self.path = path
self.user = user
self.sshKey = sshKey
}
}
struct FTPConnection: Connection {
let type: ConnectionType = ConnectionType.ftp
var path: String
var user: String
var password: String
}
struct TFTPConnection: Connection {
let type: ConnectionType = ConnectionType.tftp
var path: String
}
enum ConnectionType : Int, Codable {
case local
case sftp
case ftp
case tftp
func getType() -> Connection.Type {
switch self {
case .local: return LocalConnection.self
case .sftp: return SFTPConnection.self
case .ftp: return FTPConnection.self
case .tftp: return TFTPConnection.self
}
}
}
struct Configuration {
var from : Connection
var to : Connection
private var id = UUID.init().uuidString
var fromType : ConnectionType { return from.type }
var toType : ConnectionType { return to.type }
init(from: Connection, to: Connection) {
self.from = from
self.to = to
}
}
extension Configuration : Codable {
enum CodingKeys: String, CodingKey {
case id, from, to, fromType, toType
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
var type : ConnectionType
type = try container.decode(ConnectionType.self, forKey: .fromType)
let fromDecoder = try container.superDecoder(forKey: .from)
self.from = try type.getType().init(from: fromDecoder)
type = try container.decode(ConnectionType.self, forKey: .toType)
let toDecoder = try container.superDecoder(forKey: .to)
self.to = try type.getType().init(from: toDecoder)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.fromType, forKey: .fromType)
let fromContainer = container.superEncoder(forKey: .from)
try from.encode(to: fromContainer)
try container.encode(self.toType, forKey: .toType)
let toContainer = container.superEncoder(forKey: .to)
try to.encode(to: toContainer)
}
}
这篇关于Swift-解码/编码具有不同类型的泛型数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!