swift - 从镜像反省如何改变 child 的值(value)观

标签 swift reflection swift-structs

我在 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/

相关文章:

haskell - 如何查询当前的 GHCi 环境?

swift - swift 实际上对结构做了什么复制省略?

swift - 在 Swift 3 中访问结构之外的值

ios - Swift 动态转换因 HTTP GET 请求而失败

ios - Swift 4 TableView 获取错误的单元格

ios - 在 Mapbox 的导航 View 上添加自定义按钮

属性表达式的 C# 聚合返回 AmbiguousMatchException

hibernate - 如何从Groovy Reflection(?)获取域类值?

ios - 原子属性包装器仅在声明为类时才有效,而不是结构体

swift - UISearchResultsUpdating 未更新