Swift - Decode/encode 具有不同类型的泛型数组

Swift - Decode/encode an array of generics with different types

如何 decode/encode 不同泛型类型的数组?

我有一个数据结构,它具有符合协议 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
}

当我知道连接 FT 是什么类型时,这工作正常。但是我有一些情况,我想加载配置,不知道 FT 是哪种类型。

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

在上述情况下,FT 可以是任何未知类型,符合 Connection 协议。所以上面的函数不起作用,因为我需要在调用函数时为 FT 指定一个特定的类型,我不知道。

在第二个函数中,F实际上可以是不同的类型。这就是它变得困难的地方。我想我需要以某种方式在用户默认值中存储 FT 的类型,然后在 decodeencode 函数中使用它们(从而丢弃泛型).但我不知道如何优雅地做到这一点。

如果有任何关于如何解决此问题的想法,我将不胜感激!

以下解决方案解决了我在使用泛型时遇到的所有问题,但不知道 Connection 的具体类型。解决方案的关键是

  1. 在实现本身中保存 Connection 实现的类型
  2. 使用 superEncodersuperDecoder 到 encode/decode fromto 属性。

这是解决方案:

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)
    }
}