Swift 3 how to parse DATA

2019-03-06 06:39发布

My Swift 3 code has a Foundation Data collection of bytes (read from an SQlite BLOB). The internal content of the data has multiple blocks with this structure:

 {
     UINT32  count;  // number of points in this trkseg
     UINT32  colour; // RGB colour of the line drawn for this trkseq
     Double lat;  // latitude of 1st point
     Double long; // longitude of 1st point
     Coord  point[count-1] // array of points (2nd to last points)
}

typedef struct {
     Float lat   // difference in latitude of this point from the lat of the 1st point
     Float long  // difference in longitude of this point from the lat of the 1st point
 } Coord;

This is very easy to parse in C and Java. Unfortunately I cannot work out the best way to parse this with Swift 3. I'm not asking you how to parse this exact data layout, but just for recommendations and best practices for parsing such raw data with Swift 3. From web searches and Apple documentation I'm left very confused!

***ANSWER - Thanks to Martin R for getting me on the right track. I'm adding some code here to show how I solved this in case it will help others. As Martin said, there are many ways to solve this. My solution ensures the blob data, which is in network endian order (big endian) will always be parsed correctly regardless of the host endianness.

/// Parse the SQLite data blob to add GPS track segments
///
/// - Parameter data: GPS track information
private func addTracks(_ data: Data) {

    // The data holds compressed GPX data. It has multiple track segments.
    // It has a block of binary data per track segment with this structure:
    //     {
    //         UINT32  count;  // number of points in this trkseg
    //         UINT32  colour; // RGB colour of the line drawn for this trkseq
    //         Double lat;  // latitude of 1st point
    //         Double long; // longitude of 1st point
    //         Coord  point[count-1] // array of points (2nd to last points)
    //    }
    //
    //    typedef struct {
    //         Float lat   // difference in latitude of this point from the lat of the 1st point
    //         Float long  // difference in longitude of this point from the lat of the 1st point
    //     } Coord;

    var dataCount = data.count // number of data bytes

    var pointCount = 0     // counts coordinates per trkseg
    var colour:UInt = 0
    var lat:Double = 0.0
    var long:Double = 0.0
    var bigEndian = true
    var i = 0

    // From http://codereview.stackexchange.com/questions/114730/type-to-byte-array-conversion-in-swift
    if (NSHostByteOrder() == NS_LittleEndian) {
        bigEndian = false
    }

    while (dataCount >= 40) {
        pointCount = Int(self.uint32Value(data: data.subdata(in: i..<i+4), isBigEndian: bigEndian))
        i = i+4

        if (pointCount < 2 || ((pointCount-1)*8 + 24 > dataCount)) {
            print("ERROR, pointCount=\(pointCount)")
            break
        }
        colour = UInt(self.uint32Value(data: data.subdata(in: i..<i+4), isBigEndian: bigEndian))
        i = i+4
        let firstLat = self.doubleValue(data: data.subdata(in: i..<i+8), isBigEndian: bigEndian)
        i = i+8
        let firstLong = self.doubleValue(data: data.subdata(in: i..<i+8), isBigEndian: bigEndian)
        i = i+8
        print("pointCount=\(pointCount) colour=\(colour) firstLat=\(firstLat) firstLong=\(firstLong)")

        for _ in 1..<pointCount {
            lat = firstLat - Double(self.floatValue(data: data.subdata(in: i..<i+4), isBigEndian: bigEndian))
            i = i+4
            long = firstLong - Double(self.floatValue(data: data.subdata(in: i..<i+4), isBigEndian: bigEndian))
            i = i+4
            print("lat=\(lat) long=\(long)")
        }
        dataCount = dataCount - 24 - (pointCount-1)*8;
    }
}

private func floatValue(data: Data, isBigEndian: Bool) -> Float {
    if (isBigEndian) {
        return Float(bitPattern: UInt32(littleEndian: data.withUnsafeBytes { $0.pointee } ))
    }
    else {
        return Float(bitPattern: UInt32(bigEndian: data.withUnsafeBytes { $0.pointee }))
    }
}

private func doubleValue(data: Data, isBigEndian: Bool) -> Double {

    if (isBigEndian) {
        return Double(bitPattern: UInt64(littleEndian: data.withUnsafeBytes { $0.pointee } ))
    }
    else {
        return Double(bitPattern: UInt64(bigEndian: data.withUnsafeBytes { $0.pointee } ))
    }
}

private func uint32Value(data: Data, isBigEndian: Bool) -> UInt32 {

    if (isBigEndian) {
        return data.withUnsafeBytes{ $0.pointee }
    }
    else {
        let temp: UInt32 = data.withUnsafeBytes{ $0.pointee }
        return temp.bigEndian
    }
}

标签: ios swift swift3
1条回答
疯言疯语
2楼-- · 2019-03-06 07:16

One possible approach is to use the

public func withUnsafeBytes<ResultType, ContentType>(_ body: (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType

method to access and dereference the bytes in the data. The placeholder type ContentType can be inferred from the context:

let color: UInt32 = data.subdata(in: 0..<4).withUnsafeBytes { $0.pointee }
// ...
let lat: Double = data.subdata(in: 8..<16).withUnsafeBytes { $0.pointee }
// ...

As of Swift 4 you can use subscripting to extract the data:

let color: UInt32 = data[0..<4].withUnsafeBytes { $0.pointee }
// ...
let lat: Double = data[8..<16].withUnsafeBytes { $0.pointee }
// ...

If all fields are properly aligned for their type then you can use the

public func load<T>(fromByteOffset offset: Int = default, as type: T.Type) -> T

from UnsafeRawPointer:

data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
    let rawPointer = UnsafeRawPointer(bytes)
    let color = rawPointer.load(fromByteOffset: 0, as: UInt32.self)
    // ...
    let lat = rawPointer.load(fromByteOffset: 8, as: Double.self)
    // ...
}

On a lower level you can use memcpy which again works with arbitrarily aligned data:

var color: UInt32 = 0
var lat: Double = 0
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
    memcpy(&color, bytes, 4)
    // ...
    memcpy(&lat, bytes + 8, 8)
    // ...
}

I would probably use the first method, unless performance is an issue where you can use the second or third, depending on whether all fields are guaranteed to be aligned for their type or not.

查看更多
登录 后发表回答