Search code examples
iosswiftpointerscore-audioraw-pointer

Create a Weak UnsafeMutableRawPointer


So I'm trying to use the CoreAudio method AUGraphAddRenderNotify(...) but I need a reference to the class that created this notification within the notification block. Because this is a C function, I can't simply add a [weak self] in closure capture. Looking at the Documentation, the last parameter of this method is allowed to be an UnsafeMutableRawPointer that will be passed in during the execution of the block as the first parameter. Following this suggestion, here is the code that I have:

let selfPointer = Unmanaged.passUnretained(self).toOpaque()
AUGraphAddRenderNotify(graph, { (audioPlayerPointer, _, _, _, _, _) -> OSStatus in
    let audioPlayer = Unmanaged<AudioPlayer>.fromOpaque(audioPlayerPointer).takeUnretainedValue()
    ...

    return noErr
}, selfPointer)

Here is my question: I need to figure out how I can safely get the value behind this pointer (accounting for nil). More specifically, I want to safely access the audioPlayer and make sure that it hasn't been deallocated by the time I use it. Currently, everything works just fine until the audioPlayer gets deallocated and then my app crashes. I know that I can use AUGraphRemoveRenderNotify(...) to stop the notification before the object gets deallocated but unfortunately this method is not what I'm looking for. How can I check if the object that the pointer is pointing to has already been deallocated?

Thanks in advance!


Solution

  • Weak references don't really work like that

    Interestingly, weak references don't actually point to the target object that they model. They point to side tables, whose lifetime is different from the target object.

    • A side-table entry is allocated when the first weak reference to an object is made.
    • The creation of every weak reference increments the ref count of the side-table entry, and every destruction of a weak reference decrements it.
    • Once the target object is deallocated, the side-table entry remains in place. That way, all the weak references don't become dangling pointers.
    • After eventual deallocation of the target object, all attempts to access a weak ref to the (now-dead) cause the weak-ref to be nil-ed out (which is what you observe as a user), but also deincrements the side-table entry
    • Thus, every weak reference either needs to be destroyed (e.g. goes out of scope) or attempted to be accessed before the side table entry expires.

    Since these side table entries aren't exposed to "user land" Swift code, you can't make a raw pointer to them, and so you can't really deal with weak references in this way.

    Alternate ideas

    I have a few ideas of what you can do instead, though I haven't tried them.

    1. You can exploit UnsafeMutableRawPointer pointer to pass in a Weak<T> wrapper, like:

      struct Weak<T: AnyObject> {
          wear var object: T?
      }
      

    Though I think it'll have to be a class, in this case.

    1. or for some other mechanism, such as passing in a closure (which weakly captures the object)