Travis Griggs Travis Griggs - 4 months ago 75
Swift Question

round trip Swift number types to/from Data

With Swift 3 leaning towards

instead of
, I'm trying to ferret out what the most efficient/idiomatic way to encode/decode swifts various number types (UInt8, Double, Float, Int64, etc) as Data objects.

There's this answer for using [UInt8], but it seems to be using various pointer APIs that I can't find on Data.

I'd like to basically some custom extensions that look something like:

let input = 42.13 // implicit Double
let bytes =
let roundtrip = // --> 42.13

The part that really eludes me, I've looked through a bunch of the docs, is how I can get some sort of pointer thing (OpaquePointer or BufferPointer or UnsafePointer?) from any basic struct (which all of the numbers are). In C, I would just slap an ampersand in front of it, and there ya go.


How to create Data from a value

struct Data has an initializer

public init(bytes: UnsafePointer<UInt8>, count: Int)

which can be used in a similar way as in the various answers to the question How to convert a double into a byte array in swift? that you linked to:

let input = 42.13
var value = input
let data = withUnsafePointer(&value) {
    Data(bytes: UnsafePointer($0), count: sizeofValue(input))
print(data) // <713d0ad7 a3104540>

As @zneak already said, you can take the address only of a variable, therefore a variable copy is made with var value = value. In earlier Swift versions you could achieve that by making the function parameter itself a variable, this is not supported anymore.

However, it is easier to use the initializer

public init<SourceType>(buffer: UnsafeBufferPointer<SourceType>)


let input = 42.13
var value = input
let data = Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
print(data) // <713d0ad7 a3104540>

Note that the generic placeholder SourceType is automatically inferred from the context.

How to retrieve a value from Data

NSData had a bytes property to get access to the underlying storage. struct Data has a generic

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

instead, which can be used like this:

let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes { (ptr: UnsafePointer<Double>) -> Double in
    return ptr.pointee
print(value) // 42.13

If the ContentType can be inferred from the context then it need not be specified in the closure, so this can be simplified to

let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value: Double = data.withUnsafeBytes { $0.pointee }
print(value) // 42.13

Generic solution #1

The above conversions can now easily be implemented as generic methods of struct Data:

extension Data {

    init<T>(from value: T) {
        var value = value
        self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))

    func to<T>(type: T.Type) -> T {
        return self.withUnsafeBytes { $0.pointee }


let input = 42.13 // implicit Double
let data = Data(from: input)
print(data) // <713d0ad7 a3104540>

let roundtrip = Double.self)
print(roundtrip) // 42.13

Similarly, you can convert arrays to Data and back:

extension Data {

    init<T>(fromArray values: [T]) {
        var values = values
        self.init(buffer: UnsafeBufferPointer(start: &values, count: values.count))

    func toArray<T>(type: T.Type) -> [T] {
        return self.withUnsafeBytes {
            [T](UnsafeBufferPointer(start: $0, count: self.count/sizeof(T)))


let input: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: input)
print(data) // <0100ff7f 0080>

let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]

Generic solution #2

The above approach has one disadvantage: As in How to convert a double into a byte array in swift?, it actually works only with "simple" types like integers and floating point types. "Complex" types like Array and String have (hidden) pointers to the underlying storage and cannot be passed around by just copying the struct itself. It also would not work with reference types which are just pointers to the real object storage.

So solve that problem, one can

  • define a protocol which defines the methods for converting to Data and back:

    protocol DataConvertible {
        init(data: Data)
        var data: Data { get }
  • implement the conversions as default methods in a protocol extension:

    extension DataConvertible {
        init(data: Data) {
            self = data.withUnsafeBytes { $0.pointee }
        var data: Data {
            var value = self
            return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
  • and finally declare conformance to all types which can safely be converted to Data and back:

    extension Int : DataConvertible { }
    extension Float : DataConvertible { }
    extension Double : DataConvertible { }
    // add more types here ...

This makes the conversion even more elegant:

let input = 42.13
let data =
print(data) // <713d0ad7 a3104540>

let roundtrip = Double(data: data)
print(roundtrip) // 42.13

The advantage of the second approach is that you cannot inadvertently do unsafe conversions. The disadvantage is that you have to list all "safe" types explicitly.

You could even override the conversion methods in your own types to do whatever is necessary so serialize and deserialize a value.