解析 swift(3) 数据流的惯用方法

Idiomatic method of parsing swift(3) data streams

我正在尝试对 Swift3 数据对象进行一些简单的 BSON 解析。感觉自己在和系统作对

让我们从一些输入和方案开始:

let input = Data(bytes: [2, 0x20, 0x21, 3, 0x30, 0x31, 0x32, 1, 0x10, 4, 0x40, 0x41, 0x42, 0x43])

这只是一个简单的数据流,轻率的方案是前导字节指示有多少字节跟随构成下一个块。所以在上面,前导 2 表示 0x20、0x21 是第一个块,后面是一个包含字节 0x30、0x31、0x32 等的 3 字节块

我的第一个想法是使用流(呃,Generator,Iterator,等等)。所以我最终得到了类似的东西:

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)

这导致多个 questions/observations:

1) 为什么会有人 let 迭代器?这件事的全部意义在于跟踪集合中不断变化的位置。我真的很难理解为什么 Swift 作者选择将迭代器发送到 "all hail the value semantics" rathole。这意味着我必须将 inout 放在我所有的解析函数上。

2) 我觉得我用 IndexingIterator 过度指定了参数类型。也许我只是需要习惯冗长的泛型?

Python 结构式

对这种方法感到沮丧,我想我可能会模仿 pythons struct.unpack() 风格,其中一个元组是 return 已解析数据以及未使用数据的。因为只要我不改变数据,数据就是神奇而高效的。结果如下:

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) 我真正想要 return 的是 data[1..count], data.subdata(in: count+1..<data.count)。但是这个 return 是一个 MutableRandomAccessSlice。哪一种似乎是完全不同的类型?所以我最终使用了更复杂的 subdata.

2) 可以使用闭合的 运行ge 下标数据,但是 subdata 方法只会采用开放的 运行ge。那是怎么回事?

公开叛乱,旧习惯开始

现在很生气这个老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)

我对最终使用 POV 的解决方案感到满意。我可以充实 DataStream 来做各种事情。但是...我现在不走寻常路,感觉自己不是 "getting it"(Swift 左右的灯泡)。

TL;DR 版本

玩完之后,我发现自己很好奇什么是最惯用的流式传输数据结构的方法,根据在其中遇到的情况从中提取数据。

(免责声明:我在再次阅读 OP:s 问题时意识到(不仅仅是使用 TL;DR 眼睛 ;)这并没有真正回答 OP:s 问题,但我会留下它,因为它可能会给讨论带来一些有趣的补充;split 不在这里生成流,而是生成一大块数据序列)

这与您的"Python Struct'esque"解决方案有些相似;您可以使用 Datasplit 方法的 isSeparator 谓词闭包来解析您的字节流。

func split(maxSplits: Int = default, omittingEmptySubsequences: Bool = default, 
           isSeparator: @noescape UInt8 throws -> Bool) 
           rethrows -> [MutableRandomAccessSlice<Data>]

Returns the longest possible subsequences of the sequence, in order, that don’t contain elements satisfying the given predicate. Elements that are used to split the sequence are not returned as part of any subsequence.

来自 The Language Reference for the Data structure (Swift 3).

我自己还没来得及下载 XCode 8 beta(出于某种原因,IBM Swift 3-dec Sandbox 不包含 Data 结构),但是使用 split(...) 的示例(此处:仅用于数组)可能类似于:

/* Swift 2.2 example */
let input: [UInt8] = [2, 0x20, 0x21, 3, 0x30, 0x31, 0x32, 1, 0x10, 4, 0x40, 0x41, 0x42, 0x43]

var isSep = true
var byteCounter: (UInt8, UInt8) = (0,0)
let parsed = input.split() { elem -> Bool in
    if isSep {
        isSep = false
        byteCounter.0 = 0
        byteCounter.1 = elem
        return true
    }

    byteCounter.0 += 1
    if byteCounter.0 == byteCounter.1 {
        isSep = true // next is separator
    }
    return false

}.map { Array([=11=]) }

print(parsed) // [[32, 33], [48, 49, 50], [16], [64, 65, 66, 67]]

Swift 3 Data 结构的未经测试的等效片段:

let input = Data(bytes: [2, 0x20, 0x21, 3, 0x30, 0x31, 0x32, 1, 0x10, 4, 0x40, 0x41, 0x42, 0x43])

var isSep = true
var byteCounter: (UInt8, UInt8) = (0,0)
let parsed = input.split(maxSplits: 1000, omittingEmptySubsequences: false) { elem -> Bool in
    if isSep {
        isSep = false
        byteCounter.0 = 0
        byteCounter.1 = elem
        return true
    }
    byteCounter.0 += 1
    if byteCounter.0 == byteCounter.1 {
        isSep = true // next is separator
    }
    return false
}.map { [=12=] }

请注意,MutableRandomAccessSlice<Data> 应该 "trivially" 可转换为 DataData 本身是一个 MutableCollection 字节),比较例如到 self 上的简单映射,将 ArraySlice<Int> 转换为 Array<Int>

let foo = [1, 2, 3]
let bar = foo[0..<2]     // ArraySlice<Int>
let baz = bar.map { [=13=] } // Array<Int>

最后,如评论中所述,我选择了 DataStream class,其中包括 MartinR 的建议。这是我今天使用的实现。

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? {
        guard self.atEnd.NOT else { return nil }
        let byte = self.data[self.index]
        self.index += 1
        return byte
    }

    func next(_ count:Int) -> Data? {
        guard self.index + count <= self.data.count else { return nil }
        let subdata = self.data.subdata(in: self.index..<self.index + count)
        self.index += count
        return subdata
    }

    func upTo(_ marker:UInt8) -> Data? {
        if let end = (self.index..<self.data.count).index( where: { self.data[[=10=]] == marker } ) {
            let upTo = self.next(end - self.index)
            self.skip() // consume the marker
            return upTo
        }
        else {
            return nil
        }
    }

    func skip(_ count:Int = 1) {
        self.index += count
    }

    func skipThrough(_ marker:UInt8) {
        if let end = (self.index..<self.data.count).index( where: { self.data[[=10=]] == marker } ) {
            self.index = end + 1
        }
        else {
            self.index = self.data.count
        }
    }
}