As you saw, using an int *
is rather inefficient. That's because the assignments done to that value can't trigger any other code to run (there's no equivalent to a didSet
observer in C). So you're forced to make a separate thread whose purpose is to poll the int value periodically, which is super wasteful.
To fix this, you'll need to use a callback. These are easy in Swift, because closures are first class citizens that you can trivially create and pass around. In C, there's no such thing. The closest thing is a function pointer, but those are static, and lack the contextual storage that a closure can provide. This means that you can't pass Swift instance methods, or closures as c function pointers. You need a closure that's marked @convention(c)
, which prohibits it from capturing any state (including the self
in a method).
But there's a workaround. To understand it, first you should understand the typical callback pattern used in C.
In C it's typical for a function that takes a function pointer (as a callback) to also have a second untyped (void *
) "context" parameter. The idea is that you can manually pass in your own context, of whatever type, and it'll be stored alongside the function pointer. When it's time to trigger the callback, the C function will call your function pointer, passing in the context you gave it. Inside your callback function (the one whose pointer you passed as a callback), you have access to this void *context
, which you can then cast to your appropriate type and access whatever you need.
This context param comes by many names, such as context
, ctx
, userdata
, userinfo
, (and underscored, camelcase variants), etc.
If you update your C function to something like:
typedef void (*ProgressChangedCallback)(const int newProgress, void *userinfo);
void doSomething(ProgressChangedCallback callback, void *userinfo) {
// when the progress updates:
callback(newProgress, userinfo);
}
In Swift, you can take advantage of this userinfo
param to pass in the context you need to implement your call back. There's two popular ways to do it:
- You could use the
userinfo
param to smuggle in self
, and then call methods on self
with full access to the instance variables of the object.
- You could use the
userinfo
param to smuggle in a proper Swift closure. The closure itself can capture state, including self
or whatever else you might need.
It would look something like this:
// ProgressChangedCallback would have type `@convention(c) (Int, UnsafeMutableRawPointer) -> Void`
class MyClass {
var someInstanceState = 0
func someFunctionThatHasAccessToInstanceState() {
defer { someInstanceState += 1 }
print(someInstanceState)
// 5. This can update your progress bar UI or whatever.
}
func registerCallbackForDoSomething() {
let myProgressChangedCallback: ProgressChangedCallback = { newProgress, userInfo in
// 3. Unpack `userinfo` to get access to our instance again
let instance = Unmanaged<MyClass>.fromOpaque(userInfo).takeUnretainedValue()
// 4. Do whatever we might need with the instance
instance.someFunctionThatHasAccessToInstanceState()
}
// 1. Retain `self`, and get an opaque pointer to it
let retainedPointerToSelf = UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque())
// 2. Register our callback, and pass the pointer to self as `userInfo`
doSomething(myProgressChangedCallback, retainedPointerToSelf)
}
}
Further reading