Feb 19, 2018

Interpreting Data using Swift Generics

I’ve written previous posts on how to interpret Data as a simple scalar, or as an array of scalars. Now I want to refine that with generics.

Here’s an example of the code I want to use.

let data:Data = characteristic.value //get a data object from the CBCharacteristic
let temperature: Int16 = data.decode() //Custom method to decode into Int16

My previous solution just called a method that required specifying the pointer type for the closure parameter. In order to make this a method on Data that might return any type, it has to be generic. Here’s an implementation for “trivial” types.

extension Data {
    func decode<T>() -> T? {
        guard self.count == MemoryLayout<T>.size else {
            return nil
        }
        return self.withUnsafeBytes { (ptr: UnsafePointer<T>) -> T in
            return ptr.pointee
        }
    }
}

As long as the size of the Data matches the size of type T, it will interpret it as T. I could contraint T to be a FixedWidthInteger or some known trivial type.

What about arrays of integers? We can use a generic constraint on T to make sure we are only dealing with an array of FixedWidthIntegers, which covers all our common cases.

extension Data {
    func decode<T: FixedWidthInteger>() -> [T]? {
        let byteCount = self.count
        let stride = MemoryLayout<T>.stride
        guard byteCount % stride == 0  else {
            return nil
        }
        return self.withUnsafeBytes({ (ptr: UnsafePointer<T>) -> [T] in
            let count =  byteCount / stride
            let buffer = UnsafeBufferPointer(start: ptr, count: count)
            return Array<T>(buffer)
        })
    }
}

This time I check to make sure the bytes can be evenly divided by elements of size T. Here are some uses. Note that you need to provide type annotations to properly interpret the generic type.

let fourBytes = Data([0x01, 0x02, 0x03, 0x04])
let eightBytes = Data([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 ])

let fourByteInt8Array: [Int8]? = fourBytes.decode()
// [1, 2, 3, 4]
let fourByteInt32: Int32? = fourBytes.decode()
// 67305985
let eightByteInt8Array: [Int8]? = eightBytes.decode()
// [1, 2, 3, 4, 5, 6, 7, 8]
let eightByteInt32Array: [Int32]? = eightBytes.decode()
// [67305985, 134678021]