It appears that AVAudioPlayer is maintaining a strong reference to my containing class. Here is the essential code. Pay attention to the comments.
class StethRecording: NSObject, ObservableObject, Identifiable {
let player: AVAudioPlayer?
let id = UUID()
@Published var isPlaying = false
@Published var progress = 0.0
init(file: AVAudioFile) throws {
player = try AVAudioPlayer(contentsOf: file.url)
super.init()
// I used to assign the player delegate here.
// If I do that, when I delete this object, it
// doesn't go away.
player!.prepareToPlay()
}
deinit {
// If this object doesn't go away, I leave data.
// behind. Something I don't want to do.
try? deleteAssociatedAudioFile()
}
func play() {
guard let player else { return }
// So now I have to assign the delegate whenever
// I start playing.
player.delegate = self
isPlaying = true
player.play()
startUpdateTimer()
}
func stop() {
guard let player else { return }
player.stop()
playbackConcluded()
}
// MARK: - Private Methods
private func playbackConcluded() {
isPlaying = false
stopUpdateTimer()
updateProgress()
player!.reset()
// I also have to remove the delegate when I
// stop, for any reason.
player!.delegate = nil
player!.prepareToPlay()
}
}
extension StethRecording: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
playbackConcluded()
}
}
This works, but is this approach really necessary? I would expect the AVAudioPlayer
to use a weak reference for the delegate. Or, am I doing something else wrong here?
Hello @statemachinejunkie, thank you for your post. The delegate property of AVAudioPlayer
has the following declaration:
weak var delegate: (any AVAudioPlayerDelegate)? { get set }
So the reference is weak
. Deallocating an instance of StethRecording
should automatically release resources allocated with its player
, unless the AVAudioFile
used to construct the StethRecording
instance is being retained somewhere else.
The player isn't really using this AVAudioFile
, you could change the initializer of StethRecording
to take a URL
instead. You can also declare the player as a var
instead of a let
constant. This allows you to deallocate it (by setting it to nil
) without necessarily deallocating the underlying StethRecording
instance.