2016-03-08 1 views
2

Я делаю кучу BLE в iOS, что означает, что множество плотно упакованных структур 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> :) 

Huzzah! Работает так, как ожидалось. Возможно, вы можете добавить класс вокруг него или, возможно, расширение протокола и иметь хорошую утилиту. Проблема я против это обратный процесс:

// 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. Но я не знаю, как это можно сделать в Свифте.

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) , а затем мне просто нужно написать инициализатор, который строит их в том же порядке и набирает struct, как определено. Я предполагаю, что эта часть, которая по сути «копирует» само определение структуры (и, следовательно, подвержена ошибкам), является тем, что я надеялся использовать какую-то интроспекцию для обработки. Зеркала - единственная реальная интроспекция Swift, о которой я знаю, и она кажется довольно ограниченной.

+1

Я мог бы просто быть наивным, но я не совсем понимаю, что это касается зеркалирования, упаковки или чего-то еще. Структуры - это типы значений. Внутри 'mirror.children.forEach',' child' фактически является копией. Итак, как вы можете написать оригинал с помощью «ребенка»? У вас нет оригинала. – matt

+0

То, куда я тоже приходил. Кроме того, даже если бы это был класс, это могли быть значения 'let'. Нет никакого обещания, что это законно. Я бы, вероятно, пошел другим путем, а не пытался использовать Зеркало здесь. Возможно, вам придется написать немного больше кода, но это должно быть возможно сделать его очень механическим. –

+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 немного менее повторяющимся, но это, вероятно, так, как я бы сказал, явно перечисляя поля, которые вы хотите кодировать, а не пытаться их интроспективно. Как вы заметили, Swift интроспекция очень ограничена, и это может быть так долго. Он в основном используется для отладки и регистрации, а не для логики.

0

Я отметил, что ответ Роба является официальным ответом. Но я думал, что поделюсь тем, что я тоже сделал, вдохновленный комментариями и ответами.

Во-первыхи, я конкретизирован своей «проблема» немного включить вложенную структуру:

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 s, полученный от 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 напрямую, но я предполагаю, что есть какая-то магия fu, которая сделает это менее конкретным и все еще работает, но я еще не там.

Я играл с чем-то более питоническим, где возвращаю кортеж типа и остальной части переданного массива (а не потока/генератора), но это было не так просто использовать на верхнем уровне init уровень.

Я также попытался поместить статические методы в качестве расширений на генераторы на основе байтов, но вам нужно использовать функцию (скорее использовав вычисленный var с побочными эффектами) там, чье имя не соответствует типу, поэтому вы заканчиваете

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

Это короче, но не помещает подобные функции рядом с именами аргументов. И потребовалось бы добавить все типы имен конкретных приложений, а также расширить набор из UnpackablePrimitive.