0

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!

CentrumGuy
  • 576
  • 1
  • 4
  • 16
  • 1
    Calling AUGraphRemoveRenderNotify in the player's deinit method is a clean solution, why don't you like it? – An alternative is to create a *retained* pointer. This prevents the player from being deallocated until you explicitly release it. – Martin R Apr 15 '20 at 19:12
  • The reason I don't do it in the deinit method is because some of the code in the block runs on the main thread and might try to access the AudioPlayer after it's already been deallocated – CentrumGuy Apr 15 '20 at 19:20
  • I am not entirely sure if I understand what you mean. The code in the callback block cannot be called as soon as the notifier has been removed. And if you dispatch asynchronous code *inside* the callback then you can create a weak reference there. – Perhaps some more code demonstrating the problem would be helpful. – Martin R Apr 15 '20 at 19:36

1 Answers1

2

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)
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Yes you would need a class in order to “tunnel” the pointer to the C function. And you have to keep a reference to it. If that is an instance property of some class then you have the same problem again, unless I am mistaken. – Martin R Apr 15 '20 at 19:14
  • I tried using the first idea and creating a pointer using `UnsafeMutableRawPointer.allocate()` but after the AudioPlayer gets deallocated and I try to do `if let audioPlayer = weakWrapper.object as? AudioPlayer` I get `AURemoteIO::IOThread (11): EXC_BAD_ACCESS (code=1, address=0xeeba0dde2a60)` – CentrumGuy Apr 15 '20 at 19:33
  • @CentrumGuy could you post a minimal compilable example? You can make a dummy function to stand-in for `AUGraphAddRenderNotify`, that just takes a `@convention(c) (UnsafeMutableRawPointer) -> Void`. – Alexander Apr 15 '20 at 19:38
  • Yeah I'll do that now – CentrumGuy Apr 15 '20 at 19:42
  • Sorry I took so long. I wasn't able to get my AUGraph to generate notification callbacks in my example since in my actual project I'm using an audio stream. Nonetheless, here is the code (assuming I could get the AUGraph to generate notifications): https://gist.github.com/CentrumGuy/0fda355d7af25073d11778d61d9caf2f – CentrumGuy Apr 15 '20 at 21:00
  • @CentrumGuy I think you forgot to add `RunLoop.main.run()` at the end of that, otherwise it can't run as a standalone Swift file (it'll just terminate near instantly). It didn't crash for me, is there something I'm missing? Btw I'm not very confident in my understanding of `storeBytes(of:`, idr if that does a copy or a move, and how it works with structs and ref counted members. I would start by making `Weak` a class, and have it be externally referenced to keep it alive (just to get started). – Alexander Apr 15 '20 at 21:20
  • Or for over-the-top composability, you can make a `class Box { let value: T }`, and use that to wrap a `Weak` wrapper, to get `Box>` :P – Alexander Apr 15 '20 at 21:20
  • Thanks I'll give that a try. I forgot to tell you that it's a playground (that's how I ran it). Also, the reason it's not crashing is because `AUGraphAddRenderNotify` isn't getting called because there's no audio to render/hooked up to the graph (which I don't know how to do). That part is being done by an external library – CentrumGuy Apr 15 '20 at 21:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/211758/discussion-between-alexander-reinstate-monica-and-centrumguy). – Alexander Apr 15 '20 at 21:37
  • For anyone looking for the answer @Alexander-ReinstateMonica figured it out and put it in here: https://gist.github.com/amomchilov/a664fbd2bb0da47a219f826f2a35703e Thanks again! – CentrumGuy Apr 15 '20 at 22:41