Basically, I have a function to get a buffer of MIDIMetaEvent data that I can read in various places:
func dataBufferOfMIDIMetaEvent(_ mmePtr: UnsafePointer<MIDIMetaEvent>) -> UnsafeBufferPointer<UInt8>
The problem here is that your function signature encourages exactly the sort of behaviour that SE-0107 is trying to prevent. This function takes an
UnsafePointer<MIDIMetaEvent>
and returns an
UnsafeBufferPointer<UInt8>
for the data within it. Imagine calling it like this:
var event = MIDIMetaEvent()
let bytesBuffer = dataBufferOfMIDIMetaEvent(&event)
let bytes = bytesBuffer.baseAddress!
… stuff that accesses `event` and `bytes` …
At this point
event
and
bytes
are two pointers that point to the same memory but with different types. This is
pointer aliasing (a form of
type punning) and its behaviour is unspecified. If you rely on it, you may find that future compiler optimisations will cause your program to fail
This risk is why the SE-0107 changes are making things hard for you; Swift is trying to help you avoid future problems.
To solve this issue you must make sure that your code only accesses your memory via one type at a time. A good way to do this is
withMemoryRebound(of:capacity:body:)
. That does the following:
tells the compiler to treat the memory as holding the new type
calls the body
on return, tells the compiler to treat the memory as holding the original type
In many cases you’ll want to wrap this up in your own convenience function. For example, imagine a routine with this signature:
func withData(for: UnsafePointer<MIDIMetaEvent>, body: (_ data: UnsafeBufferPointer<UInt8>))
You can then call it like this:
var event: MIDIMetaEvent = …
withData(for: &event) { bytesBuffer in
… access `bytesBuffer` but not `event` …
}
As long as:
your code will avoid any pointer aliasing problems.
A simple implementation might look like this:
func withData(for eventPtr: UnsafePointer<MIDIMetaEvent>, body: (_ data: UnsafeBufferPointer<UInt8>) -> Void) {
let dataLength = Int(eventPtr.pointee.dataLength)
return eventPtr.withMemoryRebound(to: UInt8.self, capacity: 8 + dataLength) { eventAsBytes in
let bytesBuffer = UnsafeBufferPointer(start: eventAsBytes + 8, count: dataLength)
return body(bytesBuffer)
}
}
There’s some things to note here:
Here’s an example of it in use:
var event = MIDIMetaEvent()
event.dataLength = 1
event.data = 42
withData(for: &event) { bytesBuffer in
for b in bytesBuffer {
print(String(b, radix: 16))
// prints "2a", or 42 as hex
}
}
A more complex implementation would look like this:
func withData<ReturnType>(for eventPtr: UnsafePointer<MIDIMetaEvent>, body: (_ data: UnsafeBufferPointer<UInt8>) throws -> ReturnType) rethrows -> ReturnType {
let dataLength = Int(eventPtr.pointee.dataLength)
return try eventPtr.withMemoryRebound(to: UInt8.self, capacity: 8 + dataLength) { eventAsBytes in
let bytesBuffer = UnsafeBufferPointer(start: eventAsBytes + 8, count: dataLength)
return try body(bytesBuffer)
}
}
This has two advantages:
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"