Search code examples
swiftavaudioplayer

Is do/catch for AVAudioPlayer unnecessary?


I have a question regarding the do/catch syntax that is needed for AVAudioPlayer. What is it actually for?

Lets look at some examples of how the code more or less should look according to tutorials I have seen.

1) Here I am force unwrapping the optional NSURL in the try statement. My assumption is that if I now change the mp3 name it should call the catch block but instead I get a nil crash on the try line because the avPlayerURL property is nil. Why is it not getting catched?

  do {
        let avPlayer1URL = NSBundle.mainBundle().URLForResource("Test", withExtension: "mp3")
        avPlayer1 = try AVAudioPlayer(contentsOfURL: avPlayer1URL!)
        avPlayer1?.delegate = self
        avPlayer1?.numberOfLoops = -1
        avPlayer1?.prepareToPlay()
    } catch {
        print("Error finding AVAudioPlayer 1 file")
    }

2) I am force unwrapping the URL property directly and the result is same as 1. In this case tho it makes sense because the try line is never called. So this example would be bad coding I guess, yet a lot of tutorials show it to you this way or way 1.

    do {
        let avPlayer1URL = NSBundle.mainBundle().URLForResource("Test", withExtension: "mp3")!
        avPlayer1 = try AVAudioPlayer(contentsOfURL: avPlayer1URL)
        avPlayer1?.delegate = self
        avPlayer1?.numberOfLoops = -1
        avPlayer1?.prepareToPlay()
    } catch {
        print("Error finding AVAudioPlayer 1 file")
    }

3) So to make sure the NSURL is not nil I use optional chaining. The thing now tho is that if it cannot find the mp3 it will just not run the do block at all and therefore not the try line or catch block. Hence the whole do/catch thing in this example is not needed.

     do {
        if let avPlayer1URL = NSBundle.mainBundle().URLForResource("Test", withExtension: "mp3") {
               avPlayer1 = try AVAudioPlayer(contentsOfURL: avPlayer1URL)
               avPlayer1?.delegate = self
               avPlayer1?.numberOfLoops = -1
               avPlayer1?.prepareToPlay()
         }
    } catch {
        print("Error finding AVAudioPlayer 1 file")
    }

So my questions basically are

1) Why is the compiler telling me to use "try" (do/catch)

2) Why am I getting a crash at example 1

3) Can the AVAudioPlayer still throw an error even though I used optional chaining to make sure the mp3 exists?


Solution

  • I get a nil crash on the try line because the avPlayerURL property is nil. Why is it not getting catched?

    Swift's error handling with Do-Catch only catches errors, it does not catch exceptions.

    Unwrapping a nil Optional leads to an exception, it does not throw an error.

    Therefore this issue is not, by design, caught by catch.

    Using catch for AVAudioPlayer means that if AVAudioPlayer itself throws an error, this error will be caught.

    And to display the content of a thrown error, a nice way is to downcast the error message to an NSError like this:

    do {
        // ...
    } catch let error as NSError {
        print(error.debugDescription)
    }
    

    Let's say for example you have an MP3 file, but this file's data is corrupt and unplayable.

    You will check that the file is there like in your third example, and it will deem the file ok - but then the player will throw an error because it won't be able to read the file's content.