How to pass References from Swift to C Functions with Unmanaged Type
Discover how to pass references from Swift to C functions in your Swift app using Unmanaged type
For one of my macOS app, I want my non-active app to listen to and modified keyboard inputs. In Cocoa, we usually use the keyDown(with:) method to listen for shortcuts, but this will only work if our app is focused. Since my app is a background app, and it will never be activated, I can’t use this method.
So I turn to addGlobalMonitorForEvents(matching:handler:), but this also will not work because the docs say:
Events are delivered asynchronously to your app and you can only observe the event; you cannot modify or otherwise prevent the event from being delivered to its original target application.
So I dug deeper and found this old method: tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:). This API is from Carbon, which is older than AppKit. Here, the callback parameter is a C function, which makes it hard to work with because C functions cannot “capture” the values they depend on like Swift’s Closures.
In Swift, closures not only contains code, but also the values it depends on.
class Foo {
var bar = 1
func printBarAfterDelay() {
let closureToBeExecuted = {
print(self.bar)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: closureToBeExecuted)
}
}
In this code, we print bar
after 1 second using DispatchQueue.main.asyncAfter
. This method take a closure, and we pass the closureToBeExecuted
closure. As you can see, the closureToBeExecuted
access the bar
property by capturing the self
explicitly. Note that this may not the best way for working with closures, since it will capture self
strongly and may result in strong reference cycle.
In C, there is no such thing like Swift closure. If you capture a value in your C functions, you will get error:
A C function pointer cannot be formed from a closure that captures context
Then I came across this StackOverflow post on how we can pass reference to C functions. Usually, C functions will have a parameter of an opaque pointer (void*
). The idea is, you can convert the self
reference into an UnsafeMutableRawPointer and pass it into the this opaque pointer parameter. Then, inside the callback, you can convert the UnsafeMutableRawPointer
back into self
.
To create an UnsafeMutableRawPointer
from self
, you can do this:
let userInfo = Unmanaged.passUnretained(self).toOpaque()
passUnretained()
will create an Unmanaged, which is a type that let us manage the memory of an instance manually without Automatic Reference Counting (ARC). Then, the toOpaque()
method will create an UnsafeMutableRawPointer
. The method is named toOpaque
because it returns an opaque pointer, which is just a generic pointer without a spesific type.
Then to convert the pointer back to its type, you can do:
let instance = Unmanaged<YOUR_TYPE>.fromOpaque(userInfo!).takeUnretainedValue()