2016-03-08 38 views
2

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

因此,给定一系列不同大小的字段,我们没有得到“最紧密”的字节打包。众所周知并被接受。鉴于我的简单结构,我希望能够无需填充或对齐的东西就可以任意编码字段。实际上比较容易:

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

UPDATE

正如意见建议,我可以做类似如下:

// 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其中元素== UINT8) ,然后我只需要编写一个初始化程序,将这些字符串以相同的顺序排序并将结构定义为。我猜那部分本质上是“复制”了结构定义本身(因此容易出错),这是我希望用某种内省来处理的。镜子是我意识到的唯一真正的斯威夫特反思,它似乎相当有限。

+1

我可能只是天真的,但我不太明白这与镜像,打包或其他任何事情有关。结构是值类型。在'mirror.children.forEach'中,'child'实际上是一个副本。那么你怎么能通过“孩子”的方式回复原作?你没有原始的。 – matt

+0

我刚到的地方也是如此。此外,即使它是一个班级,这些也可能是“让”值。没有任何承诺,这是合法的。我可能会换一种方式,而不是尝试在这里使用镜像。您可能需要编写更多的代码,但应该可以使其非常机械。 –

+0

不,我不认为你天真@matt :)我想要一个更好的问题标题,你有一个建议?通常,在我写出问题时,标题变得更清晰。但有时候并非如此。 –

回答

2

正如评论中所讨论的,我怀疑这太过聪明。 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方法重复性稍差一点,但这仍然可能是我的方式,明确列出要编码的字段而不是试图反思它们。正如你所看到的,斯威夫特的反思是非常有限的,并且可能很长一段时间。它主要用于调试和日志记录,而不是逻辑。

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种不同类型的有符号整数,但可以添加更多。例如,在我自己的代码中,我有一些来自UInt8Enum,我在其中添加了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,在那里我返回了一个类型的元组和一个通过数组的其余部分(而不是流/发生器),但是这并不像在顶层使用那么容易init的等级。

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

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

这是较短的,但并没有把喜欢的功能类型旁边的参数名。并且需要添加各种应用程序特定的方法名称以及一个扩展了UnpackablePrimitive的集合。