我在 iOS 中做了一堆 BLE,这意味着很多紧凑的 C 结构被编码/解码为字节数据包。以下操场片段说明了我一般尝试做的事情。
import Foundation
// THE PROBLEM
struct Thing {
var a:UInt8 = 0
var b:UInt32 = 0
var c:UInt8 = 0
}
sizeof(Thing) // --> 9 :(
var thing = Thing(a: 0x42, b: 0xDEADBEAF, c: 0x13)
var data = NSData(bytes: &thing, length: sizeof(Thing)) // --> <42000000 afbeadde 13> :(
所以给定一系列不同大小的字段,我们没有得到“最紧密”的字节包装。相当知名和接受。鉴于我的简单结构,我希望能够在没有填充或对齐的情况下背靠背任意编码字段。实际上相对容易:
// ARBITRARY PACKING
var mirror = Mirror(reflecting: thing)
var output:[UInt8] = []
mirror.children.forEach { (label, child) in
switch child {
case let value as UInt32:
(0...3).forEach { output.append(UInt8((value >> ($0 * 8)) & 0xFF)) }
case let value as UInt8:
output.append(value)
default:
print("Don't know how to serialize \(child.dynamicType) (field \(label))")
}
}
output.count // --> 6 :)
data = NSData(bytes: &output, length: output.count) // --> <42afbead de13> :)
嘘!按预期工作。可能会在它周围添加一个类,或者可能是一个协议扩展并有一个很好的实用程序。我遇到的问题是相反的过程:
// ARBITRARY DEPACKING
var input = output.generate()
var thing2 = Thing()
"\(thing2.a), \(thing2.b), \(thing2.c)" // --> "0, 0, 0"
mirror = Mirror(reflecting:thing2)
mirror.children.forEach { (label, child) in
switch child {
case let oldValue as UInt8:
let newValue = input.next()!
print("new value for \(label!) would be \(newValue)")
// *(&child) = newValue // HOW TO DO THIS IN SWIFT??
case let oldValue as UInt32: // do little endian
var newValue:UInt32 = 0
(0...3).forEach {
newValue |= UInt32(input.next()!) << UInt32($0 * 8)
}
print("new value for \(label!) would be \(newValue)")
// *(&child) = newValue // HOW TO DO THIS IN SWIFT??
default:
print("skipping field \(label) of type \(child.dynamicType)")
}
}
给定一个未填充的结构值,我可以适当地解码字节流,找出每个字段的新值。我不知道该怎么做是用新值实际更新目标结构。在上面的示例中,我展示了如何使用 C 来执行此操作,获取指向原始子项的指针,然后使用新值更新其值。我可以在 Python/Smalltalk/Ruby 中轻松完成。但我不知道如何在 Swift 中做到这一点。
更新
正如评论中所建议的,我可以执行以下操作:
// SPECIFIC DEPACKING
extension GeneratorType where Element == UInt8 {
mutating func _UInt8() -> UInt8 {
return self.next()!
}
mutating func _UInt32() -> UInt32 {
var result:UInt32 = 0
(0...3).forEach {
result |= UInt32(self.next()!) << UInt32($0 * 8)
}
return result
}
}
extension Thing {
init(inout input:IndexingGenerator<[UInt8]>) {
self.init(a: input._UInt8(), b: input._UInt32(), c: input._UInt8())
}
}
input = output.generate()
let thing3 = Thing(input: &input)
"\(thing3.a), \(thing3.b), \(thing3.c)" // --> "66, 3735928495, 19"
基本上,我将各种流解码方法移动到字节流(即 Element == UInt8 的 GeneratorType),然后我只需要编写一个初始化程序,以相同的顺序将这些方法串起来,并将结构定义为类型。我想那部分,本质上是“复制”结构定义本身(因此容易出错),是我希望使用某种内省来处理的。镜子是我所知道的唯一真正的 Swift 内省,而且它似乎非常有限。