Home > database >  Share retain state across multiple Unmanaged instances
Share retain state across multiple Unmanaged instances

Time:11-13

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.

  • Related