是否有可能使“任何协议”符合 Codable?

gre*_*rey 8 swift swift-protocols codable

我正在尝试在 Swift 5.7 中创建一个Codable结构体[any Protocol]

struct Account: Codable {
    var id: String
    var name: String
    var wallets: [any Wallet]
}

protocol Wallet: Codable {
    var id: String { get set }
    //... other codable properties
}
Run Code Online (Sandbox Code Playgroud)

但是,即使Wallet符合Codable

类型“帐户”不符合协议“可解码”

类型“帐户”不符合协议“可编码”

是否可以做出any符合Codable

或者 Swift 5.7 仍然无法做到这一点?

编辑正如回答的那样,您必须实现自己的一致性。这就是我的做法:

protocol Wallet: Identifiable, Codable {
    var id: String { get set }
}

struct DigitalWallet: Wallet {
    var id: String
    // other properties
}

struct CashWallet: Wallet {
    var id: String
    // other properties
}

struct Account {
    var id: String
    var name: String
    var wallets: [any Wallet]
}

extension Account: Codable {
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case digitalWallet
        case cashWallet
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(String.self, forKey: .id)
        name = try values.decode(String.self, forKey: .name)

        let dW = try values.decode([DigitalWallet].self, forKey: .digitalWallet)
        let cW = try values.decode([CashWallet].self, forKey: .cashWallet)
        wallets = []
        wallets.append(contentsOf: dW)
        wallets.append(contentsOf: cW)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)

        var digitalWallet: [DigitalWallet] = []
        var cashWallet: [CashWallet] = []

        wallets.forEach { wallet in
            if wallet is DigitalWallet {
                digitalWallet.append(wallet as! DigitalWallet)
            } else if wallet is CashWallet {
                cashWallet.append(wallet as! CashWallet)
            }
        }
        try container.encode(digitalWallet, forKey: .digitalWallet)
        try container.encode(cashWallet, forKey: .cashWallet)
    }
}
Run Code Online (Sandbox Code Playgroud)

但我已经恢复为只使用这个:

struct Account: Codable {
    var id: String
    var name: String
    var cashWallets: [CashWallet]
    var digitalWallets: [DigitalWallet]
}
Run Code Online (Sandbox Code Playgroud)

any Protocol我担心仅仅为了遵守依赖倒置原则而付出的性能开销是不值得的。

Rob*_*ier 6

协议不符合自身,因此any Wallet本身不是可编码的,因此您不会在这里获得自动一致性,也不会获得数组的自动处理。但你可以自己轻松处理:

extension Account: Encodable {
    enum CodingKeys: CodingKey {
        case id
        case name
        case wallets
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.id, forKey: .id)
        try container.encode(self.name, forKey: .name)

        var walletContainer = container.nestedUnkeyedContainer(forKey: .wallets)
        for wallet in wallets {
            try walletContainer.encode(wallet)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您暂时注释掉wallets,Xcode 14 会自动为您完成剩余的方法encode(to:),因此您只需编写 for 的循环即可wallets

这是未来可能会改善的事情。Swift 无法自动生成这种一致性并没有什么深层次的原因。只是今天没有。

然而,正如 Alexander 所指出的,不可能以一般方式符合 Decodable,因为它需要一个init. 您必须知道要解码哪种类型,并且序列化数据不包含该信息(至少按照您描述的方式)。因此,您必须手写做出选择(例如,使用某种默认的钱包类型)。

  • “所以‘any Wallet’本身是不可编码的”天哪,我对‘any’关键字非常高兴。简单地添加这句话使得区分“Wallet”协议和“Wallet”存在变得更加容易! (2认同)