如何从镜像内省中更改子项的值

时间:2016-03-08 16:54:50

标签: swift reflection swift-structs

我在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>    :(

因此,考虑到一系列不同规模的领域,我们不会得到最严格的&#34;&#34;包装的字节。相当知名和接受。鉴于我的简单结构,我希望能够在没有填充或对齐的情况下背靠背地任意编码字段。实际上比较容易:

// 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)")
    }
}

给定一个未填充的struct值,我可以适当地解码字节流,找出每个字段的新值。我不知道该怎么做是用新值实际更新目标结构。在上面的示例中,我将展示如何使用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"

基本上,我将各种流解码方法移动到字节流(即GeneratorType,其中Element == UInt8),然后我只需编写一个初始化程序,以相同的顺序将这些字符串串起来,并将结构定义为。我想那部分,基本上是&#34;复制&#34;结构定义本身(因此容易出错)是我希望使用某种内省来处理的。镜子是我所知道的唯一真正的Swift内省,看起来非常有限。

2 个答案:

答案 0 :(得分:2)

正如评论中所讨论的,我怀疑这是过于聪明。 Swift包含许多对此方法不友好的类型。我将重点关注如何使样板尽可能简单,而不必担心消除它。例如,这非常草率,但我可能会朝着这个方向前进:

从一些helper packer / unpacker函数开始:

$ nl -b a blankfile.txt
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15  sometext
    16
    17
    18
    19
    20

然后打电话给他们:

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 } } 方法重复性降低一点,但这仍然可能是我要去的方式,明确列出你想要编码的字段而不是试图反省它们。正如你所注意到的,Swift内省是非常有限的,并且它可能会持续很长时间。它主要用于调试和记录,而不是逻辑。

答案 1 :(得分:0)

我已经标记了Rob的答案是正式答案。但是我想我会分享我最终做的事情,受到评论和答案的启发。

首先,我充实地填充了我的“问题”以包含嵌套结构:

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
    }
}

能够“打包”东西很容易将它们添加到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。老实说,我不确定基于价值的发电机的价值是什么,因为传递它们几乎总是想要分享状态。对于捕获数据流位置的对象进行重新整理的一点是,能够共享它。我也不喜欢直接指定IndexingGenerator,但我想有一些神奇的魔法可以使它不那么具体而且仍然可以工作,但我还没有。

我确实玩了更多pythonic的东西,我返回了一个类型的元组和传递的数组的剩余部分(而不是流/生成器),但这在顶层使用并不是那么容易{ {1}}级别。

我也尝试将静态方法作为基于字节的生成器的扩展,但你必须使用一个函数(宁愿使用带有副作用的计算var),其名称与类型不匹配,所以你最终像

这样的东西
init

这个更短,但不会将类似函数的类型放在参数名称旁边。并且需要添加各种特定于应用程序的方法名称以及扩展self.init(a: bytes._UInt8(), b: bytes._UInt32(), inner: Inner(packed:&bytes), c: bytes._UInt8()) s的集合。