Swift-解码/编码具有不同类型的泛型数组

时间:2019-12-16 22:10:54

标签: swift generics encoding decode codable

如何解码/编码不同通用类型的数组?

我有一个数据结构,该结构具有符合协议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函数中使用它们(因此要丢弃泛型)。但是我不知道如何优雅地做到这一点。

对于解决此问题的任何想法,我将不胜感激!

1 个答案:

答案 0 :(得分:1)

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

  1. 在实现本身中保存Connection实现的类型,并且
  2. 使用superEncodersuperDecoderfromto属性进行编码/解码。

这是解决方案:

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