【问题标题】:How to change the value of a child from a Mirror introspection如何从镜像内省中改变孩子的价值
【发布时间】:2016-03-08 16:54:49
【问题描述】:

我在 iOS 中做了一堆 BLE,这意味着很多紧凑的 C 结构被编码/解码为字节数据包。以下操场 sn-ps 说明了我一般尝试做的事情。

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 中做到这一点。

更新

按照 cmets 中的建议,我可以执行以下操作:

// 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 内省,而且它似乎非常有限。

【问题讨论】:

  • 我可能太天真了,但我不太明白这与镜像、打包或其他任何事情有什么关系。结构是值类型。在mirror.children.forEach 中,child 实际上是一个副本。那么你怎么可能通过child 写回原件呢?你没有原件。
  • 也正是我要去的地方。此外,即使它是一个类,这些也可能是 let 值。根本无法保证这是合法的。我可能会采用另一种方式,而不是尝试在这里使用 Mirror。您可能需要编写更多代码,但应该可以让它变得非常机械。
  • 不,我不认为你很天真@matt :) 我想要一个更好的问题标题,你有什么建议吗?通常,当我写出问题时,标题会变得更加清晰。但有时不是那么多。

标签: swift reflection swift-structs


【解决方案1】:

正如 cmets 中所讨论的,我怀疑这过于聪明了。 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 内省非常有限,而且可能会持续很长时间。它主要用于调试和日志记录,而不是逻辑。

【讨论】:

    【解决方案2】:

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

    首先,我稍微充实了我的“问题”以包含一个嵌套结构:

    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 种不同类型的有符号整数,但可以添加更多。例如,在我自己的代码中,我有一些从UInt8 派生的Enums,我添加了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,但我想有一些 fu 魔法会使它变得不那么具体并且仍然有效,但我还没有。

    我确实玩过一些更 Pythonic 的东西,我返回一个类型的元组和传递的数组的其余部分(而不是流/生成器),但这在顶层使用起来并不容易@ 987654334@级别。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-22
      • 2014-06-13
      • 1970-01-01
      • 2017-05-08
      • 1970-01-01
      相关资源
      最近更新 更多