使用 Swift 网络框架,如何使 IPv4Address 和 IPv6Address 符合 Codable?

With the Swift Network Framework, how can I make IPv4Address and IPv6Address conform to Codable?

Swift 网络框架包括结构 IPv4AddressIPv6Address。它们与 NWEndpoints 一起用于网络连接。 IPv6Address 结构对于验证 IPv6 地址语法和实施缩短规则也很有用。

如何使 IPv4AddressIPv6Address 符合 Codable?

诀窍是使用 IPv4Address.rawValue 或 IPv6Address.rawValue 字段在 Data() 结构中获取 IPv4 或 IPv6 地址。然后对数据进行编码。

解码时,可以使用Data初始化地址,处理初始化失败的情况。

也可以使用 IPv6Address.description 或 IPv6Address.debugDescription 进行编码,但不推荐这样做,因为这些描述将来可能会更改格式(thx Martin R)。

import Foundation
import Network

extension IPv6Address: Codable {
    enum CodingKeys: String, CodingKey {
        case ipv6Data
    }
    enum IPv6AddressDecodingError: Error {
        case decoding(String)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        let addressData = self.rawValue
        try container.encode(addressData, forKey: .ipv6Data)
    }

    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let addressData = try values.decode(Data.self, forKey: .ipv6Data)
        guard let ipv6Address = IPv6Address(addressData) else {
            throw IPv6AddressDecodingError.decoding("unable to decode IPv6 address from \(values)")
        }
        self = ipv6Address
    }
}

IPv4Address 本质上相同:

import Foundation
import Network

extension IPv4Address: Codable {
    enum CodingKeys: String, CodingKey {
        case ipv4Data
    }
    enum IPv4AddressDecodingError: Error {
        case decoding(String)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        let addressData = self.rawValue
        try container.encode(addressData, forKey: .ipv4Data)
    }

    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let addressData = try values.decode(Data.self, forKey: .ipv4Data)
        guard let ipv4Address = IPv4Address(addressData) else {
            throw IPv4AddressDecodingError.decoding("unable to decode IPv4 address from \(values)")
        }
        self = ipv4Address
    }
}

无需创建您自己的 IPv4AddressDecodingError。您可以使用其 dataCorruptedError 方法抛出 DecodingError。顺便说一句,不需要为单个值创建 CodingKeys 枚举:

您还可以创建一个符合 RawRepresentable & Codable 的协议并将 RawValue 约束为 Codable。这样您就可以为两个 ip 地址创建通用编码器和解码器方法:

import Network
public protocol RawRepresentableCodableProtocol: RawRepresentable & Codable
                                                 where Self.RawValue: Codable { }

public extension RawRepresentableCodableProtocol {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let rawValue = try container.decode(RawValue.self)
        guard let object = Self(rawValue: rawValue) else {
            throw DecodingError
                .dataCorruptedError(in: container, debugDescription: "Invalid rawValue data: \(rawValue)")
        }
        self = object
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(rawValue)
    }
}

现在我们可以扩展 RawRepresentableCodableProtocol 约束 SelfIPAddress 协议并提供一个易出错的 rawValue 数据初始化器:

public extension RawRepresentableCodableProtocol where Self: IPAddress {
     init?(rawValue: Data) {
        guard let object = Self(rawValue, nil) else { return nil }
        self = object
    }
}

extension IPv4Address: RawRepresentableCodableProtocol { }
extension IPv6Address: RawRepresentableCodableProtocol { }

游乐场测试:

let ipv4 = IPv4Address("1.2.33.44")!                                         // 1.2.33.44
let dataIPv4 = try JSONEncoder().encode(ipv4)                                // 10 bytes
let loadedIPv4 = try JSONDecoder().decode(IPv4Address.self, from: dataIPv4)  // 1.2.33.44

let ipv6 = IPv6Address("2001:db8::35:44")!                                   // 2001:db8::35:44
let dataIPv6 = try JSONEncoder().encode(ipv6)                                // 26 bytes
let loadedIPv6 = try JSONDecoder().decode(IPv6Address.self, from: dataIPv6)  // 2001:db8::35:44