swift Decoder是否需要具有键的外部数据表示?

jmo0nnb3  于 2023-09-30  发布在  Swift
关注(0)|答案(2)|浏览(122)

我做了什么?**
我读了苹果的documentation的主题。

详情

Swift标准库定义了数据编码和解码的标准化方法。
我意识到,我遇到的所有示例都是从外部数据表示中解码数据,这些数据表示具有JSONPLISTXML(自定义)等键。
我遇到了一个用例,其中外部数据表示没有键。例如,当从蓝牙设备阅读数据时。您接收的数据格式为ArrayUInt8
在研究Apple的文档和自定义Decoder实现时,我意识到所有外部数据表示都有键。
我想知道苹果的解决方案是否留有足够的回旋余地来处理不使用密钥的外部数据表示,即UInt8Array

ac1kyiln

ac1kyiln1#

我想你可能误解了Codable API的用途。它不是用来编写解析和编写二进制数据的逻辑的--您仍然需要自己编写。
Codable允许您以更抽象的方式编码和解码,通过键控,非键控和单值容器。你可以这样说:

  • 得到一个带有这些键的带键容器
  • 获取无键容器
  • 用YYY键编码XXX
  • 用XXX键将值解码为YYY类型
  • 将XXX编码为该无键容器的下一个值
  • 将这个无键容器的下一个值解码为XXX类型

你写你希望你的模型如何根据这些操作进行编码/解码,然后Decoder/Encoder将对底层的二进制数据做相应的事情。
例如,如果你想把一个有5个整数的JSON数组解码成一个有5个Int属性的结构体,你可以这样写:

struct MyModel: Decodable {
    let a, b, c, d, e: Int
    
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        a = try container.decode(Int.self)
        b = try container.decode(Int.self)
        c = try container.decode(Int.self)
        d = try container.decode(Int.self)
        e = try container.decode(Int.self)
    }
}

对于JSON,unkeyedContainer()表示“准备解码JSON数组”,“decode(Int.self)”表示“将下一个数组元素读取为Int”。请注意,您不必编写如何准确解析JSON。
Decoder/Encoder实现不必支持所有这些操作。如果实现由于任何原因不支持某个方法,那么当调用该方法时,它们可以抛出错误。
对于一系列字节,您可以实现自己的Decoder,它只支持unkeyedContainer()decode(T.self)将根据T读取x个字节,并将其转换为正确的类型,有点像BinaryReader

// just rough sketch of an implementation, not complete
struct _BinaryDecoder: Decoder, UnkeyedDecodingContainer {
    var isAtEnd: Bool {
        data.count >= currentIndex
    }
    
    var currentIndex: Int = 0
    
    func hasNBytes(_ n: Int) -> Bool {
        data.count - currentIndex >= n
    }
    
    mutating func decode(_ type: Float.Type) throws -> Float {
        guard hasNBytes(4) else { throw someError }
        let f = data.withUnsafeBytes { buffer in
            buffer.loadUnaligned(fromByteOffset: currentIndex, as: Float.self)
        }
        currentIndex += 4
        return f
    }
    
    mutating func decode(_ type: Int32.Type) throws -> Int32 {
        guard hasNBytes(4) else { throw someError }
        let i = data.withUnsafeBytes { buffer in
            buffer.loadUnaligned(fromByteOffset: currentIndex, as: Int32.self)
        }
        currentIndex += 4
        return i
    }
    
    let data: Data
    
    func unkeyedContainer() throws -> UnkeyedDecodingContainer {
        self
    }
}

struct BinaryDecoder {
    func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
        try T.init(from: _BinaryDecoder(data: data))
    }
}

然后,您可以在模型中实现init(from:),如下所示:

var container = try decoder.unkeyedContainer()
let flags = try container.decode(UInt16.self)
switch flags {
    // use container.decode to initialise different properties based on flags
    // e.g. if the next bytes represent two 32-bit signed ints, and then a float
    case 0:
        property1 = try container.decode(Int32.self)
        property2 = try container.decode(Int32.self)
        property3 = try container.decode(Float.self)
}

但是,如果您只打算解码这一种类型的二进制数据,那么在不使用Codable的情况下编写解码逻辑可能更容易。

y53ybaqx

y53ybaqx2#

Decoder.container(keyedBy:)被记录为如果Decoder没有可用于解码的键控数据,则抛出错误(DecodingError.typeMismatch)。在这种情况下,如果Decoder表示的格式根本无法支持键控数据,那么当请求键控数据时,它总是会抛出错误。
但是,这样的Decoder对于任意类型都不是特别有用,因为大多数类型默认对键控数据进行编码和解码;这是使用编译器的合成Encodable/Decodable一致性时的默认行为。
如果您使用这样的Decoder只解码您控制的类型(并且知道只请求无键容器),那么完全避免Codable API并编写自己的编码和解码工具可能会更简单,更容易。
根据您对格式的描述,这种格式不太可能适合Codable,反之亦然。(这也很好!Codable并不适合所有数据格式。)

相关问题