如何从镜像内省更改 child 的值

How to change the value of a child from a Mirror introspection

我在 iOS 中做了一堆 BLE,这意味着很多紧凑的 C 结构被 encoded/decoded 作为字节数据包。以下 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>    :(

所以给定一系列不同大小的字段,我们没有得到 "tightest" 字节打包。相当知名和接受。考虑到我的简单结构,我希望能够在没有填充或对齐的情况下对字段进行任意编码。其实比较容易:

// 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 >> ([=12=] * 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>   :)

万岁!按预期工作。可能会在它周围添加一个 Class,或者可能是一个协议扩展,并有一个很好的实用程序。我遇到的问题是相反的过程:

// 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([=13=] * 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([=14=] * 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),然后我只需要编写一个初始化程序,以相同的顺序将它们串起来并键入定义的结构作为。我想那部分,本质上是 "copying" 结构定义本身(因此容易出错),是我希望使用某种内省来处理的部分。镜子是我所知道的唯一真实的 Swift 内省,而且它似乎非常有限。

正如评论中所讨论的那样,我怀疑这太聪明了。 Swift 包括很多对这种方法不友好的类型。相反,我会专注于如何使样板文件尽可能简单,而不用担心删除它。例如,这很草率,但在我可能会去的方向上:

从一些辅助 packer/unpacker 函数开始:

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 >> ([=10=] * 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([=10=] * 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 内省是非常有限的,并且可能会持续很长时间。它主要用于调试和日志记录,而不是逻辑。

我已将 Rob 的答案标记为官方答案。但我想我也会分享我最终所做的事情,受到评论和答案的启发。

首先,我充实了我的 "Problem" 以包含嵌套结构:

struct Inner {
    var ai:UInt16 = 0
    var bi:UInt8 = 0
}

struct Thing {
    var a:UInt8 = 0
    var b:UInt32 = 0
    var inner = Inner()
    var c:UInt8 = 0
}

sizeof(Thing) // -->   12   :(
var thing = Thing(a: 0x42, b: 0xDEADBEAF, inner: Inner(ai: 0x1122, bi: 0xDD), c: 0x13)
var data = NSData(bytes: &thing, length: sizeof(Thing)) // -->   <42000000 afbeadde 2211dd13>    :(

对于任意打包,我坚持使用相同的通用方法:

protocol Packable {
    func packed() -> [UInt8]
}

extension UInt8:Packable {
    func packed() -> [UInt8] {
        return [self]
    }
}

extension UInt16:Packable {
    func packed() -> [UInt8] {
        return [(UInt8((self >> 0) & 0xFF)), (UInt8((self >> 8) & 0xFF))]
    }
}

extension UInt32:Packable {
    func packed() -> [UInt8] {
        return [(UInt8((self >> 0) & 0xFF)), (UInt8((self >> 8) & 0xFF)), (UInt8((self >> 16) & 0xFF)), (UInt8((self >> 24) & 0xFF))]
    }
}

extension Packable {
    func packed() -> [UInt8] {
        let mirror = Mirror(reflecting:self)
        var bytes:[UInt8] = []
        mirror.children.forEach { (label, child) in
            switch child {
            case let value as Packable:
                bytes += value.packed()
            default:
                print("Don't know how to serialize \(child.dynamicType) (field \(label))")
            }
        }
        return bytes
    }
}

能够 "pack" 事情就像将它们添加到 Packable 协议并告诉它们自己 pack 一样容易。对于我上面的案例,我只需要 3 种不同类型的有符号整数,但可以添加更多。例如,在我自己的代码中,我有一些 Enum 派生自 UInt8,我向其中添加了 packed 方法。

extension Thing:Packable { }
extension Inner:Packable { }

var output = thing.packed()
output.count // -->   9   :)
data = NSData(bytes: &output, length: output.count) // -->   <42afbead de2211dd 13>   :)

为了能够解压东西,我想出了一点点支持:

protocol UnpackablePrimitive {
    static func unpack(inout input:IndexingGenerator<[UInt8]>) -> Self
}

extension UInt8:UnpackablePrimitive {
    static func unpack(inout input:IndexingGenerator<[UInt8]>) -> UInt8 {
        return input.next()!
    }
}

extension UInt16:UnpackablePrimitive {
    static func unpack(inout input:IndexingGenerator<[UInt8]>) -> UInt16 {
        return UInt16(input.next()!) | (UInt16(input.next()!) << 8)
    }
}

extension UInt32:UnpackablePrimitive {
    static func unpack(inout input:IndexingGenerator<[UInt8]>) -> UInt32 {
        return UInt32(input.next()!) | (UInt32(input.next()!) << 8) | (UInt32(input.next()!) << 16) | (UInt32(input.next()!) << 24)
    }
}

有了这个,我就可以将初始化器添加到我的高级结构中,例如

extension Inner:Unpackable {
    init(inout packed bytes:IndexingGenerator<[UInt8]>) {
        self.init(ai: UInt16.unpack(&bytes), bi: UInt8.unpack(&bytes))
    }
}

extension Thing:Unpackable {
    init(inout packed bytes:IndexingGenerator<[UInt8]>) {
        self.init(a: UInt8.unpack(&bytes), b: UInt32.unpack(&bytes), inner: Inner(packed:&bytes), c: UInt8.unpack(&bytes))
    }
}

我喜欢的一点是这些初始化器以与定义结构相同的顺序和类型调用默认初始化器。因此,如果结构的类型或顺序发生变化,我必须重新访问 (packed:) 初始化程序。孩子长了点,不过也不算长。

我不喜欢的是必须到处传递 inout。老实说,我不确定基于价值的生成器的价值是什么,因为将它们传递给你几乎总是想要共享状态。具体化捕获数据流位置的 object 的全部意义在于能够共享它。我也不喜欢直接指定 IndexingGenerator,但我想有一些魔法可以使它不那么具体并且仍然有效,但我还没有。

我确实玩过一些更 pythonic 的东西,我 return 类型的元组和传递数组的其余部分(而不是 stream/generator),但这还差得远在顶级 init 级别易于使用。

我也试过将静态方法作为基于字节的生成器的扩展,但是你必须使用一个名称与类型不匹配的函数(宁愿使用具有副作用的计算变量),所以你最终得到类似

的东西
self.init(a: bytes._UInt8(), b: bytes._UInt32(), inner: Inner(packed:&bytes), c: bytes._UInt8())

这更短,但没有将类似函数的类型放在参数名称旁边。并且需要添加各种特定于应用程序的方法名称以及一个扩展 UnpackablePrimitives 的集合。