Я делаю кучу 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, о которой я знаю, и она кажется довольно ограниченной.
Я мог бы просто быть наивным, но я не совсем понимаю, что это касается зеркалирования, упаковки или чего-то еще. Структуры - это типы значений. Внутри 'mirror.children.forEach',' child' фактически является копией. Итак, как вы можете написать оригинал с помощью «ребенка»? У вас нет оригинала. – matt
То, куда я тоже приходил. Кроме того, даже если бы это был класс, это могли быть значения 'let'. Нет никакого обещания, что это законно. Я бы, вероятно, пошел другим путем, а не пытался использовать Зеркало здесь. Возможно, вам придется написать немного больше кода, но это должно быть возможно сделать его очень механическим. –
Нет, я не думаю, что ты наивный @matt :) Мне бы понравился лучший вопрос, у вас есть предложение? Часто, когда я пишу вопрос, заголовок становится яснее. Но иногда не так много. –