我在 iOS 中做了很多 BLE,这意味着很多紧凑的 C 结构被编码/解码为字节数据包。以下 playground 片段说明了我一般尝试做的事情。
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> :)
万岁!按预期工作。可能会在它周围添加一个类,或者可能是一个协议(protocol)扩展并有一个很好的实用程序。我遇到的问题是相反的过程:
// 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 来完成它,获取指向原始 child 的指针,然后用新值更新它的值。我可以在 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"
基本上,我将各种流解码方法移动到字节流(即 GeneratorType,其中 Element == UInt8),然后我只需要编写一个初始化程序,以相同的顺序将它们串起来,并将结构定义为类型。我想那部分本质上是“复制”结构定义本身(因此容易出错),这正是我希望使用某种内省(introspection)来处理的部分。镜像是我所知道的唯一真正的 Swift 自省(introspection),而且它似乎非常有限。
最佳答案
正如评论中所讨论的,我怀疑这太聪明了。 Swift 包含很多对这种方法不友好的类型。相反,我会专注于如何使样板文件尽可能简单,而不用担心删除它。例如,这是非常草率的,但在我可能会去的方向上:
从一些辅助打包/解包函数开始:
func pack(values: Any...) -> [UInt8]{
var output:[UInt8] = []
for value in values {
switch value {
case let i as UInt32:
(0...3).forEach { output.append(UInt8((i >> ($0 * 8)) & 0xFF)) }
case let i as UInt8:
output.append(i)
default:
assertionFailure("Don't know how to serialize \(value.dynamicType)")
}
}
return output
}
func unpack<T>(bytes: AnyGenerator<UInt8>, inout target: T) throws {
switch target {
case is UInt32:
var newValue: UInt32 = 0
(0...3).forEach {
newValue |= UInt32(bytes.next()!) << UInt32($0 * 8)
}
target = newValue as! T
case is UInt8:
target = bytes.next()! as! T
default:
// Should throw an error here probably
assertionFailure("Don't know how to deserialize \(target.dynamicType)")
}
}
然后调用他们:
struct Thing {
var a:UInt8 = 0
var b:UInt32 = 0
var c:UInt8 = 0
func encode() -> [UInt8] {
return pack(a, b, c)
}
static func decode(bytes: [UInt8]) throws -> Thing {
var thing = Thing()
let g = anyGenerator(bytes.generate())
try unpack(g, target: &thing.a)
try unpack(g, target: &thing.b)
try unpack(g, target: &thing.c)
return thing
}
}
多一点思考可能会使 decode
方法的重复性降低一点,但这可能仍然是我要走的路,明确列出您想要编码的字段而不是尝试反省他们。正如您所注意到的,Swift 的内省(introspection)是非常有限的,而且可能会持续很长时间。它主要用于调试和日志记录,而不是逻辑。
关于swift - 从镜像反省如何改变 child 的值(value)观,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35873301/