Here’s a short and sweet extension that converts a CoreBluetooth CBUUID to a Foundation UUID. It’s interesting mainly because of the time it took me to figure out how to use the raw data.1 I had to get familiar with pointer functions in Swift.2
UUID can be initialzed with a uuid_t. I just had to find a way to convert a CBUUID to a uuid_t. In the end it turned out to be pretty short and simple. Here are the steps:
-
CBUUIDhas adataproperty that returns aDataobject. -
Datahas a methodwithUnsafeByteswhich gives me anUnsafeRawBufferPointerto work on in the context of a closure. I can load that data in that buffer as auuid_t. - UUID has a constructor that takes a
uuid_t. I can use that to construct a newUUIDwhich is returned from the closure.
Here’s my code:
// Swift 5.3 extension CBUUID { var UUIDValue: UUID? { get { guard self.data.count == MemoryLayout<uuid_t>.size else { return nil } return self.data.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> UUID in let uuid = pointer.load(as: uuid_t.self) return UUID(uuid: uuid) } } } }
A few notes on what I learned from this:
- Swift sees
uuid_tas a 16 element tuple ofUInt8s. Because of that, my first attempt used anUnsafePointer<UInt8>. -
NSUUIDhas a constructor not available inUUID:init(uuidBytes bytes: UnsafePointer<UInt8>!)For no particularly good reason, I wanted a path to directly construct aUUID. - I can manually make a tuple by indexing through an
UnsafeBufferPointer<UInt8>, like(buffer[0], buffer[1], ...). That felt inelegant. - I can load a
uuid_tbecause the data is layout compatible with the data provided byCBUUID. That is, it has the same elements, the same length, in the same order. -
CBUUIDsupports some UUIDs which are shorter than 16 bytes. Usually these are defined in the Bluetooth standard. These are not layout compatible withuuid_t. Instead of converting these to their 16 byte equivalents, I cop out and return nil.
-
I could have gone from a
CBUUID→String→UUID, but that seems unnecessary. A uuid is essentially an array of 16 bytes, and I should be able to change the representation of those bytes without a back-and-forth converstion to String. ↩ -
The version of
withUsafeBytesthat passes anUnsafePointerto its closure was deprecated in Swift 5. The replacement passes anUnsafeRawBufferPointer. ↩