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

时间:2016-06-27 18:21:57

标签: swift3 swift-data

我正在尝试对Swift3 Data对象进行一些简单的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字节块。

我的第一个想法是用流(呃,发电机,迭代器,等等)来做。所以我最终得到了类似的东西:

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作者选择将迭代器发送到“所有值得语义”的rathole。这意味着我必须将inout放在我的所有解析函数上。

2)我觉得我过度使用IndexingIterator指定参数类型。也许我只需要习惯于详细的泛型?

Python Struct'esque

对这种方法感到沮丧,我想我可能会模仿pythons struct.unpack()样式,其中返回解析数据的元组以及未使用的数据。因为据说数据是​​神奇而有效的,只要我不改变它们。结果如下:

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方法仅采用开放范围。那是什么?

打开叛乱,旧习惯开始

现在很恼火,这个老小的跟踪者似乎无法在这里找到快乐,我只是自己动手:

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来做各种各样的事情。但是......我现在已经走出了人迹罕至的路,觉得我不是“得到它”(Swiftish灯泡)。

TL; DR版

在此之后,我发现自己很好奇通过数据结构流式传输数据结构的最惯用方法是什么,根据它们遇到的内容从中提取数据。

2 个答案:

答案 0 :(得分:1)

最后,正如评论中所提到的,我参加了DataStream课程,其中包括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[$0] == 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[$0] == marker } ) {
            self.index = end + 1
        }
        else {
            self.index = self.data.count
        }
    }
}

答案 1 :(得分:0)

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

这与您的&#34; Python Struct&#34; esque&#34; 解决方案有些类似;您可以使用isSeparator split Data方法的func split(maxSplits: Int = default, omittingEmptySubsequences: Bool = default, isSeparator: @noescape UInt8 throws -> Bool) rethrows -> [MutableRandomAccessSlice<Data>] 谓词闭包来解析字节流。

Data
     

按顺序返回序列中可能的最长子序列,   不包含满足给定谓词的元素。分子   用于拆分序列的任何部分都不会返回   亚序列。

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

我没有时间自己下载XCode 8 beta(由于某些原因,IBM Swift 3-dec Sandbox不包含split(...)结构),但是使用{{1}的示例(这里:仅用于数组)可能看起来像以下几行:

/* 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($0) }

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 { $0 }

请注意MutableRandomAccessSlice<Data>应该是&#34;平凡的&#34;可转换为DataData本身是MutableCollection个字节),例如,到自我的简单地图,将ArraySlice<Int>转换为Array<Int>

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