Search code examples
iosswiftswift2ios9

Reusable extension to play sound without memory leak in iOS and Swift?


Playing a sound is so verbose in code and would like to create an extension of AVAudioSession if possible. The way I'm doing it is assigning an object to a variable, but need help/advise on how to set up this function so it's reusable and optimized.

Here's what I have:

func playSound(name: String, extension: String = "mp3") {
    let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)

    do {
        try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
        try AVAudioSession.sharedInstance().setActive(true)

        UIApplication.sharedApplication().beginReceivingRemoteControlEvents()

        audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
        audioPlayer?.prepareToPlay()
        audioPlayer?.play()
    } catch { }
}

I think I have to create the audioPlayer variable outside of this function, otherwise I had a hard time playing anything. Maybe it can be self contained? I'm hoping to use it something like this:

AVAudioSession.sharedInstance().play("bebop")

Solution

  • Taking the code straight from your example I see two options:

    1) Is there any particular reason why you want to make it as an extension of AVAudioSession? If not, just make your own service!

    class AudioPlayerService {
    
        static let sharedInstance = AudioPlayerService()
    
        var audioPlayer: AVAudioPlayer?
    
        func playSound(name: String, extension: String = "mp3") {
            let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
    
            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
                try AVAudioSession.sharedInstance().setActive(true)
    
                UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
    
                audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
                audioPlayer?.prepareToPlay()
                audioPlayer?.play()
            } catch { }
        }
    
    }
    

    2) If you do need to make it as an extension of AVAudioSession, then take a look at associated objects

    extension AVAudioSession {
    
        private struct AssociatedKeys {
            static var AudioPlayerTag = "AudioPlayerTag"
        }
    
        var audioPlayer: AVAudioPlayer? {
            get {
                return objc_getAssociatedObject(self, &AssociatedKeys.AudioPlayerTag) as? AVAudioPlayer
            }
    
            set {
                if let newValue = newValue {
                    objc_setAssociatedObject(
                        self,
                        &AssociatedKeys.AudioPlayerTag,
                        newValue as AVAudioPlayer?,
                        .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                    )
                }
            }
        }
    
        func playSound(name: String, extension: String = "mp3") {
            let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
    
            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
                try AVAudioSession.sharedInstance().setActive(true)
    
                UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
    
                audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
                audioPlayer?.prepareToPlay()
                audioPlayer?.play()
            } catch { }
        }
    
    }