I'm invoking a C library from Swift that takes in an opaque pointer and a callback function. The C library then passes that opaque pointer to the callback function. In order to create the opaque pointer, I'm using the Unmanaged type provided by the Swift standard library, which looks something like this:
func callback(
opaque: UnsafeMutableRawPointer?,
) {
let queue: DispatchQueue = // ...
let thing = Unmanaged<MyThing>.fromOpaque(opaque).takeRetainedValue()
queue.async { [weak thing] in
guard let thing else { return }
// Use thing here
}
}
func newThing() -> MyThing {
let thing = MyThing()
let opaque = Unmanaged.passUnretained(thing).toOpaque()
my_c_library_function(opaque, callback) // invokes callback(opaque)
return thing
}
class MyThing {
}
The issue I'm having is that thing
gets deallocated at the end of callback
. I guess this is because I create a new Unmanaged instance, call takeRetained, and then the value is released at the end of callback. The fact that I still have a reference to thing
inside of newThing
isn't considered as part of the Unmanaged
instance in callback
.
Is there a way to solve this problem? The main issue I'm trying to solve with this pattern is getting a pointer to MyThing back from the C library, and then using that pointer in an async block. Ideally I'd like to only perform the async block if the caller to newThing
still had a reference to it.
CodePudding user response:
I think the issue is that I'm passing unretained, but then taking retained so the retain count is incorrect.
Indeed, this is one of the issues. If the C function is synchronous, you should pass unretained and take unretained, because there is no need to retain a reference. The instance of myThing
will remain alive because newThing
is retaining it, when callback
is called.
However, the way you are using queue.async
is problematic. You should not weakly capture thing
there. Since queue.async
is asynchronous, it will run after callback
has returned. At that point, the instance will already have been deallocated, and your guard
will fail and return immediately.
You should just do:
queue.async {
// do things with thing, e.g.
print(thing)
}
This causes the closure passed to async
to retain thing
.
If the C function is asynchronous, you should pass retained and take retained instead. This is because in this case the callback will run after newThing
returns, at which point nothing will be retaining the instance of thing
.
Similarly, you also should not weakly capture thing
in queue.async
.