Jul 28, 2019

Turning Data into Arrays in Swift 5

An earlier article discussed how to take Data and turn it into an Array of some simple type. Swift 5 deprecated the method withUnsafeBytes that passes an UnsafePointer<T> to the closure. Instead, it includes a version of that method that passes an UnsafeBufferPointer to the closure. This makes the counter-example from that post less attractive, but it doesn't make it any clearer how to get your bytes out.

An UnsafeRawBufferPointer is an untyped pointer, meaning you have to explicitly tell Swift the type of the elements it points to. The way to do that is with bindMemory(to:).

The idea of binding memory doesn't exist in a lot of programming languages. You are telling the compiler, I am going to access this memory as if it were type T. The Swift type system can then verify your access into the memory is correct according to the bound type1.

So our updated plan is:

  1. Access an UnsafeRawBufferPointer with the method withUnsafeBytes
  2. Bind the UnsafeRawBufferPointer to create a typed UnsafeBufferPointer
  3. Use the UnsafeBufferPointer to create an Array.
let data = Data(bytes: [0x01, 0x00, 0x00, 0x00,
                        0x02, 0x00, 0x00, 0x00])

let array = data.withUnsafeBytes {
                    (pointer: UnsafeRawBufferPointer) -> [Int8] in
    let bytes = pointer.bindMemory(to: Int8.self)
    return Array<Int8>(bytes)
}

// array == [1, 0, 0, 0, 2, 0, 0, 0]

The code above interprets the data as 8 1-byte values. It's just as valid to interpret that data as 2 4-byte values, like below.

let data = Data(bytes: [0x01, 0x00, 0x00, 0x00,
                        0x02, 0x00, 0x00, 0x00])

let array = data.withUnsafeBytes {
                    (pointer: UnsafeRawBufferPointer) -> [Int32] in
    let bytes = pointer.bindMemory(to: Int32.self)
    return Array<Int8>(bytes)
}

// array == [1, 2]

As with decoding numbers, if you received data from a Bluetooth device with CoreBluetooth, there is no information to tell you how to decode the object. There's also no guarantee the order of the bytes will match the expected values in the Int32 example above. It's necessary to know ahead of time what the Data object represents.


  1. You could technically bind memory to two different types, and the compiler would let you. Doing so is undefined behavior.