loading series of struct from data in Swift

Viewed 120

I am not sure where I got this piece of code. But here I have some code to get series of Vertex(or whatever) out of Data in swift.

struct Vertex {
    var x, y, z, w: Float16
    var r, g, b, a: Float16
}

extension Data {
    func elements<T>() -> [T] {
        return withUnsafeBytes {
            Array(UnsafeBufferPointer<T>(start: $0, count: count/MemoryLayout<T>.stride))
        }
    }
}

It works fine to me, but I have this warning. I spent some time, but I cannot figure this out. So could someone please help me out?

'withUnsafeBytes' is deprecated: use `withUnsafeBytes<R>(_: (UnsafeRawBufferPointer) throws -> R) rethrows -> R` instead

By the way, I am trying to save and load large number of data with this way with pairing of following piece of code.

extension Array {
    var data: Data {
        var value = self
        return NSData(bytes: &value, length: MemoryLayout<Element>.stride * self.count) as Data
    }
}

Thank you,

  • EDIT

Here is the code what I like to do, it works fine, but I like to get rid of warning...

struct Vertex: Equatable {
    var x, y, z, w: Float16
    var r, g, b, a: Float16
    // assumption: no precision error 
    static func == (lhs: Self, rhs: Self) -> Bool {
        return  lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w &&
                lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a
    }
}
let v0 = Vertex(x: 1, y: 2, z: 3, w: 4, r: 0, g: 0.25, b: 0.5, a: 0.75)
let v1 = Vertex(x: 5, y: 6, z: 7, w: 8, r: 0.2, g: 0.4, b: 0.6, a: 0.8)
let v2 = Vertex(x: 9, y: 0, z: 1, w: 2, r: 0.5, g: 0.75, b: 0.0, a: 1.0)
let original: [Vertex] = [v0, v1, v2]
let data = original.data
print(data as NSData)
let restored: [Vertex] = data.elements()
let w0 = restored[0]
let w1 = restored[1]
let w2 = restored[2]
print(v0 == w0)
print(v1 == w1)
print(v2 == w2)
2 Answers

First make your struct conform to ContiguousBytes:

extension Vertex: ContiguousBytes {
    func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
        try Swift.withUnsafeBytes(of: self) { try body($0) }
    }
}

Then create a custom initializer on ContiguousBytes to allow initializing any type that conforms to it with contiguous bytes:

extension ContiguousBytes {
    init<T: ContiguousBytes>(_ bytes: T) {
        self = bytes.withUnsafeBytes { $0.load(as: Self.self) }
    }
}

To extract the bytes/data from the types that conform to it:

extension ContiguousBytes {
    var bytes: [UInt8] { withUnsafeBytes { .init($0) } }
    var data: Data { withUnsafeBytes { .init($0) } }
}

Now you can simply make the magic happen. Playground testing:

struct Vertex {
    var x, y, z, w: Float16
    var r, g, b, a: Float16
}

let vertex = Vertex(x: 1.2, y: 2.3, z: 3.4, w: 4.5, r: 0.5, g: 0.6, b: 0.7, a: 1)
let bytes = vertex.bytes   // [205, 60, 154, 64, 205, 66, 128, 68, 0, 56, 205, 56, 154, 57, 0, 60]
let loadedVertex = Vertex(bytes)
print(loadedVertex)   // Vertex(x: 1.2, y: 2.3, z: 3.4, w: 4.5, r: 0.5, g: 0.6, b: 0.7, a: 1.0)

edit/update:

to convert your bytes to a collection of vertices:

extension Array {
    var data: Data {
        var value = self
        return .init(bytes: &value, count: MemoryLayout<Element>.stride * count)
    }
}

extension ContiguousBytes {
    func objects<T>() -> [T] { withUnsafeBytes { .init($0.bindMemory(to: T.self)) } }
    var vertices: [Vertex] { objects() }
}

let vertex1 = Vertex(x: 1.2, y: 2.3, z: 3.4, w: 4.5, r: 0.5, g: 0.6, b: 0.7, a: 1)
let vertex2 = Vertex(x: 2.3, y: 3.4, z: 4.5, w: 5.6, r: 1, g: 0.8, b: 1, a: 1)
let data = [vertex1, vertex2].data
let loadedVertices = data.vertices

print(loadedVertices)   // [__lldb_expr_8.Vertex(x: 1.2, y: 2.3, z: 3.4, w: 4.5, r: 0.5, g: 0.6, b: 0.7, a: 1.0), __lldb_expr_8.Vertex(x: 2.3, y: 3.4, z: 4.5, w: 5.6, r: 1.0, g: 0.8, b: 1.0, a: 1.0)]

To mirror what is being done in .data I believe you just want to copy the bytes directly into the Array. This is all pretty dangerous, because it's making some pretty strong assumptions around memory management (if T, or any property of T were a class or included hidden classes internally, the reference counts could be wrong). So I'd be really uncomfortable making this a generic extension like this. But for Float16 it's probably fine, assuming that you never encounter an endian issue (or an architecture that pads differently).

But to just do what you're doing, I'd recommend:

extension Data {
    func elements<T>() -> [T] {
        Array(unsafeUninitializedCapacity: count/MemoryLayout<T>.stride) { buffer, initializedCount in
            copyBytes(to: buffer)
            initializedCount = buffer.count
        }
    }
}
Related