Я пытаюсь сделать простой анализ BSON объектов Swift3 Data. Я чувствую, что борюсь с системой.
Давайте начнем с некоторых входных данных и схемы:
let input = Data(bytes: [2, 0x20, 0x21, 3, 0x30, 0x31, 0x32, 1, 0x10, 4, 0x40, 0x41, 0x42, 0x43])
Это всего лишь простой поток данных, причем легкомысленная схема заключается в том, что начальный байт указывает, сколько байтов следует за ним, образуя следующий фрагмент. Таким образом, в приведенном выше примере первая цифра 2 указывает на то, что 0x20, 0x21 являются первым блоком, за которым следует 3-байтовый блок, содержащий байты 0x30, 0x31, 0x32 и т. д.
Потоки
Моя первая мысль — сделать это с потоком (эм, генератор, итератор, что угодно). Итак, я получаю что-то вроде:
var iter = input.makeIterator()
func parse(_ stream:inout IndexingIterator<Data>) -> Data {
var result = Data()
if let count = stream.next() {
for _ in 0..<count {
result.append(Data(bytes:[stream.next()!]))
}
}
return result
}
parse(&iter)
parse(&iter)
parse(&iter)
parse(&iter)
Это приводит к нескольким вопросам/наблюдениям:
1) Зачем кому-то let
итератор? Весь смысл этой штуки в том, чтобы отслеживать эволюцию позиции над коллекцией. Я действительно борюсь с тем, почему авторы Swift решили отправить итераторы в дыру «все приветствуют семантику значений». Это означает, что я должен поставить inout
на все мои функции синтаксического анализа.
2) Мне кажется, что я слишком указываю тип аргумента с помощью IndexingIterator. Может быть, мне просто нужно привыкнуть к многословным дженерикам?
Структура в стиле Python
Разочарованный таким подходом, я подумал, что мог бы эмулировать стиль struct.unpack() python, где кортеж возвращается из проанализированных данных, а также неиспользуемых данных. Поскольку якобы Данные волшебны и эффективны, пока я их не мутирую. Получилось так:
func parse2(_ data:Data) -> (Data, Data) {
let count = Int(data[0])
return (data.subdata(in: 1..<count+1), data.subdata(in: count+1..<data.count))
}
var remaining = input
var chunk = Data()
(chunk, rest) = parse2(remaining)
chunk
(chunk, rest) = parse2(remaining)
chunk
(chunk, rest) = parse2(remaining)
chunk
(chunk, rest) = parse2(remaining)
chunk
Я столкнулся с двумя проблемами с этим.
1) Что мне очень хотелось вернуть, так это data[1..count], data.subdata(in: count+1..<data.count)
. Но это возвращает MutableRandomAccessSlice. Который кажется совершенно другим типом? Так что в итоге я использовал более сложный subdata
.
2) Можно подписать данные с закрытым диапазоном, но метод subdata
будет использовать только открытый диапазон. Что с этим?
Открытый бунт, старые привычки вступают в силу
Раздраженный тем, что этот старый Smalltalker, кажется, не может найти здесь счастья, я просто сворачиваю свой собственный:
class DataStream {
let data:Data
var index = 0
var atEnd:Bool {
return index >= self.data.count
}
init(data:Data) {
self.data = data
}
func next() -> UInt8 {
let byte = self.data[self.index]
self.index += 1
return byte
}
func next(_ count:Int) -> Data {
let subdata = self.data.subdata(in: self.index..<self.index + count)
self.index += count
return subdata
}
}
func parse3(_ stream:DataStream) -> Data {
let count = Int(stream.next())
return stream.next(count)
}
let stream = DataStream(data: input)
parse3(stream)
parse3(stream)
parse3(stream)
parse3(stream)
Это решение, которым я доволен с точки зрения конечного использования. Я могу конкретизировать DataStream, чтобы делать все что угодно. Но... теперь я сбился с проторенной дорожки и чувствую, что не «понимаю» (лампочка Swiftish).
TL;версия DR
После этой игры мне стало любопытно, какой самый идиоматический способ потоковой передачи через структуры данных, извлечения из них данных на основе того, что в них встречается.
Data
предпочтительно по сравнению с[UInt8]
s в будущем. Основной фактор моего разочарования заключается в том, почему Итераторы лучше в качестве структур типа значений, а не объектов ссылочного типа. - person Travis Griggs   schedule 30.06.2016nil
. - person Martin R   schedule 30.06.2016