0

Given a variable of type Data, how do I copy 16 bytes out of it and directly into a variable of type uuid_t?

I'm writing some Swift code that exchanges data with an external service using Google Protocol Buffers. The service returns a data structure that contains two properties: an Int representing a count and an Array of raw bytes Uint8 representing sequential UUIDs of 16 bytes each. In Swift, this is represented as the following struct:

struct UUIDCollection {
  var count:Int         // Number of 16 byte identifiers.
  var identifiers:Data  // Array of bytes where every group of 16 bytes, starting at index 0, is a uuid. 
}

I'm unable to figure out the correct usage of Swift pointers to allow me to do something like this:

for i in 0..<count {
  let offset = Int(i * 16)

  var bytes:uuid_t
  let range = offset..<(offset + 16)

  withUnsafeMutablePointer(to:&bytes) { (b:UnsafeMutablePointer<UInt8>) -> Void in
    identifiers.copyBytes(to:b, from:range)

    let uuid = UUID(uuid:bytes)
    print("UUID: \(uuid.uuidString)")
  }
}

The Xcode error I receive is:

Cannot convert value of type '(UnsafeMutablePointer) -> Void' to expected argument type '(UnsafeMutablePointer<_>) -> _'

What is the correct, and ideally most efficient, way of converting such an array of bytes into an array of uuid_t?

Note: The Swift code is designed to work with an existing API, which vends identifiers as a single array of bytes. Changing that API to vend a vector of identifiers or a vector of string UUIDs isn't really an option at the moment.

kennyc
  • 4,654
  • 3
  • 27
  • 43
  • see here : https://www.cocoaphile.com/posts/turning-data-into-arrays-in-swift.html – Eric Dec 01 '19 at 16:05
  • and here too : https://stackoverflow.com/questions/45149413/converting-swift-data-into-int16 – Eric Dec 01 '19 at 16:15
  • Thanks Eric. In those examples, and others I've seen, they all return a new Array. I'm curious how I can copy directly into an existing `uuid_t`, which is technically a tuple of 16 bytes and, to my understanding, laid out as a contiguous 16 byte array in memory. Creating a new array then copying into a `uuid_t` seems like an unnecessary step. – kennyc Dec 01 '19 at 16:20

1 Answers1

3

You can stride your identifiers subdata and load your UUIDs as follow:

extension Data {
    func object<T>() -> T { withUnsafeBytes { $0.load(as: T.self) } }
}

extension UUIDCollection {
    var uuids: [UUID] {
        stride(from: 0, to: count * 16, by: 16)
            .map { identifiers[$0..<$0.advanced(by: 16)].object() }
    }
}
Leo Dabus
  • 198,248
  • 51
  • 423
  • 494
  • 2
    That's brilliant, thank you. Of note is that when dealing with millions of items, I get a decent speed improvement by only making one initial all to `withUnsafeBytes` and then using `subscript []` notation with the `stride` values to manually create a `uuid_t`. Looking at Instruments, this performance improvement appears to come from having significantly less calls to `release` and `retain`. For smaller datasets it's likely irrelevant, but for millions of UUIDs, it's noticeable. – kennyc Dec 01 '19 at 18:59