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
}
}
One possible approach is to use the
method to access and dereference the bytes in the data. The placeholder type
ContentType
can be inferred from the context:As of Swift 4 you can use subscripting to extract the data:
If all fields are properly aligned for their type then you can use the
from
UnsafeRawPointer
:On a lower level you can use
memcpy
which again works with arbitrarily aligned data: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.