In what I think is a common pattern, I'm trying to interface with some C code that accepts
- a handler function that the C code will call and
- a pointer to some user-provided data
Here's some (not real) code demonstrating the basic setup. The user creates a Manager
class and provides it with a callback. When the user calls Manager.run
, some C code is triggered that then calls the user's callback:
class Manager {
private let pointer: SomeCPointer
var userCallback: () -> Void = { }
init() {
func callback(userData: UnsafeRawPointer) {
let manager = Unmanaged<Manager>.fromOpaque(userData).takeUnretainedValue()
manager.userCallback()
}
self.pointer = initSomeCType()
pointer.pointee.callback = callback
pointer.pointee.userData = Unmanaged.passUnretained(self).toOpaque()
}
deinit {
free(pointer)
}
func run() {
someCFunction(pointer)
}
}
There's two parts of this code that I want to zoom in on. Manager.run
:
func run() {
someCFunction(pointer)
}
and the function callback
:
func callback(userData: UnsafeRawPointer) {
let manager = Unmanaged<Manager>.fromOpaque(userData).takeUnretainedValue()
manager.userCallback()
}
someCFunction
may call the callback any number of times (oftentimes a lot).
So run
basically becomes a tight loop of:
let manager = Unmanaged<Manager>.fromOpaque(userData).takeUnretainedValue()
manager.userCallback()
When profiling this code, there's two big penalties around accessing manager
and userCallback
:
+0x5c bl "DYLD-STUB$$swift_beginAccess"
+0x60 ldp x23, x20, [x19, #0x18]
+0x64 mov x0, x21
+0x68 bl "DYLD-STUB$$swift_bridgeObjectRetain"
+0x6c mov x0, x20
+0x70 bl "DYLD-STUB$$swift_retain"
+0x74 add x0, sp, #0x60
+0x78 blr x23
+0x7c mov x0, x20
+0x80 bl "DYLD-STUB$$swift_release"
// ...
+0xa4 bl "DYLD-STUB$$swift_bridgeObjectRelease"
- It's wrapped by Swift exclusivity enforcement, and
- Retain/release calls
Exclusivity enforcement I could probably eliminate by making userCallback
immutable in some fashion (though that comes with its own pains). The retain/release around manager
and userCallback
though Swift seems to have fewer tools to deal with (and to my surprise none of Unmanaged
's APIs actually eliminate retain/release calls from the call site.)
Are there any tools or strategies for this sort of situation that I'm missing?