尝试初始化 AVAudioPlayer 后因展开的 nil 值而崩溃

Crash with unwrapped nil value after attempting to initialize an AVAudioPlayer

//
//  playSoundsViewController.swift
//  recordVoice
//
//  Created by david on 11/08/15.
//  Copyright (c) 2015 Tomcorp. All rights reserved.
//

import UIKit
import AVFoundation


class playSoundsViewController: UIViewController {

    var audioPlayer:AVAudioPlayer!
    var receivedAudio:RecordedAudio!

    override func viewDidLoad() {
        super.viewDidLoad()

        audioPlayer = AVAudioPlayer(contentsOfURL: receivedAudio.filePathUrl, error: nil)
        audioPlayer.enableRate = true
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    @IBAction func slowVoice(sender: UIButton) {
        audioPlayer.stop()
        audioPlayer.rate = 0.5
        audioPlayer.currentTime = 0.0
        audioPlayer.play()
    }

    @IBAction func fastVoice(sender: UIButton) {
            audioPlayer.stop()
            audioPlayer.rate = 2.0
            audioPlayer.currentTime = 0.0
            audioPlayer.play()
    }


    @IBAction func stopButtonSounds(sender: UIButton) {
            audioPlayer.stop()
    }

    }

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */


//
//  recordSoundsViewController.swift
//  recordVoice
//
//  Created by david on 11/08/15.
//  Copyright (c) 2015 Tomcorp. All rights reserved.
//

import UIKit
import AVFoundation


class recordSoundsViewController: UIViewController, AVAudioRecorderDelegate {

    @IBOutlet weak var recordButton: UIButton!
    @IBOutlet weak var recordingLabel: UILabel!
    @IBOutlet weak var stopButton: UIButton!

    var audioRecorder:AVAudioRecorder!
    var recordedAudio:RecordedAudio!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewWillAppear(animated: Bool) {

        stopButton.hidden = true
        recordButton.enabled = true

    }

    @IBAction func recordButton(sender: UIButton) {

        recordButton.enabled = false
        stopButton.hidden = false
        recordingLabel.hidden = false
        //record voice



        //Inside func recordAudio(sender: UIButton)
        let dirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String


        let recordingName = "my_audio.wav"
        let pathArray = [dirPath, recordingName]
        let filePath = NSURL.fileURLWithPathComponents(pathArray)
        println(filePath)

        var session = AVAudioSession.sharedInstance()
        session.setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)

        audioRecorder = AVAudioRecorder(URL: filePath, settings: nil, error: nil)
        audioRecorder.delegate = self
        audioRecorder.meteringEnabled = true
        audioRecorder.record()

    }

    func audioRecorderDidFinishRecording(recorder: AVAudioRecorder!, successfully flag: Bool) {
        if(flag){
        recordedAudio = RecordedAudio()
        recordedAudio.filePathUrl = recorder.url
        recordedAudio.title = recorder.url.lastPathComponent
        self.performSegueWithIdentifier("stopRecording", sender: recordedAudio)
        }else {
            println("Recording was not successfully")
            recordButton.enabled = true
            stopButton.hidden = true
        }
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if (segue.identifier == "StopRecording"){
            let playSoundsVC:playSoundsViewController = segue.destinationViewController as! playSoundsViewController
            let data = sender as! RecordedAudio
            playSoundsVC.receivedAudio = data

        }
    }

    @IBAction func stopButton(sender: UIButton) {
        recordButton.enabled = true
        recordingLabel.hidden = true
        //Inside func stopAudio(sender: UIButton)
        audioRecorder.stop()
        var audioSession = AVAudioSession.sharedInstance()
        audioSession.setActive(false, error: nil)
    }

}

//
//  RecordedAudio.swift
//  recordVoice
//
//  Created by david on 13/08/15.
//  Copyright (c) 2015 Tomcorp. All rights reserved.
//

import Foundation

class RecordedAudio: NSObject{
    var filePathUrl: NSURL!
    var title: String!
}

//
//  AppDelegate.swift
//  recordVoice
//
//  Created by david on 11/08/15.
//  Copyright (c) 2015 Tomcorp. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}

控制台上是这样写的:

可选(文件:///Users/david/Library/Developer/CoreSimulator/Devices/12D7F78E-5FFA-4964-9AEE-F395D962264F/data/Containers/Data/Application/A5B7545C-2800-495C-B016-07C3DF010D97/Documents/my_audio.wav) 致命错误:在展开可选值

时意外发现 nil

TOMCORP,您的代码充满新手错误。有些直接导致崩溃,有些迟早会产生问题,有些只是未能跟上良好的编程习惯。

一切都在评论中解释。

如果不出意外,请务必理解 "MAJOR ERROR 2",因为这与 ViewController 生命周期有关。在 segue 标识符上弄错大写也是一个严重错误。

顺便说一句,从回放 VC 看,似乎没有 return 的规定。您需要某种按钮来执行 dismissViewControllerAnimated().

以下代码在我的 Xcode 6.4 / iPhone 模拟器 8.4 上运行良好。我遗漏了样板 AppDelegate 代码。还要确保放回我在下面遗漏的 'import' 语句。

import UIKit
import AVFoundation

// FIX of Error 11
let stopRecordingSegue = "stopRecording"

// Error 1: (Style) Class names should always be capitalized
class PlaySoundsViewController: UIViewController {
    var audioPlayer: AVAudioPlayer?

    // MAJOR ERROR 2: viewDidLoad() runs **BEFORE** prepareForSegue(). So it was looking for the
    // 'recordedAudio' instance variable but prepareForSegue had NOT YET had a chance to
    // initialize it.

    // viewDidLoad is the WRONG place for DYNAMIC, RUNTIME initialization.

    // Instead we make a custom method, called by prepareForSegue(), so we're in full control
    // of the flow of data from VC to VC.
    func prepareAudio(receivedAudio: RecordedAudio) {
        var err: NSError?
        // Error 3: check carefully for all error conditions
        if let ap = AVAudioPlayer(contentsOfURL: receivedAudio.filePathUrl, error: &err) {
            audioPlayer = ap
            ap.enableRate = true
        }
        else {
            assertionFailure("Exiting program, couldn't create recorder: \(err?.localizedDescription)")
        }
    }

    // Error 4: (Style) Don't have overridden methods that do nothign but call super().
    // The clutter just creates confusion. didRecieveMemoryWarning function removed.

    @IBAction func slowVoice(sender: UIButton) {
        // Error 5: Handle case where player did not get initialized
        if let ap = audioPlayer {
            ap.stop()
            ap.rate = 0.5
            ap.currentTime = 0.0
            ap.play()
        }
        else {
            println("No audioplayer available in slowVoice!")
        }
    }

    @IBAction func fastVoice(sender: UIButton) {
        // Error 5: Handle case where player did not get initialized
        if let ap = audioPlayer {
            ap.stop()
            ap.rate = 2.0
            ap.currentTime = 0.0
            ap.play()
        }
        else {
            println("No audioplayer available in fastVoice!")
        }
    }

    @IBAction func stopButtonSounds(sender: UIButton) {
        // Error 5: Handle case where player did not get initialized
        if let ap = audioPlayer {
            ap.stop()
        }
        else {
            println("No audioplayer available in stop!")
        }
    }

    // Error 6: (Style) Leave out dead code that's commented out. Creates more confusing clutter.
}

class RecordSoundsViewController: UIViewController, AVAudioRecorderDelegate {
    @IBOutlet weak var recordButton: UIButton!
    @IBOutlet weak var recordingLabel: UILabel!
    @IBOutlet weak var stopButton: UIButton!

    var audioRecorder: AVAudioRecorder!
    var recordedAudio: RecordedAudio!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Error 7: Initialization: All one-time initialization must go in viewDidLoad(), not when the user
        // pressed a button. Sometimes redundant initialization is not actually harmful, but it's a terrible
        // programming habit nonetheless.
        let dirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String

        let recordingName = "my_audio.wav"
        let pathArray = [dirPath, recordingName]
        let filePath = NSURL.fileURLWithPathComponents(pathArray)
        println(filePath)

        // Error 8: Don't create unnecessary variables just to do one-time initialization. Confusing clutter.
        AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, error: nil)

        // Error 9: Don't use 'nil' for error object; receive the error and check it
        var err: NSError?
        audioRecorder = AVAudioRecorder(URL: filePath, settings: nil, error: &err)
        if let e = err {
            assertionFailure("Exiting program, couldn't create recorder: \(e.localizedDescription)")
        }
        else {
            println("successfully created audio recorder")
            audioRecorder.delegate = self
            audioRecorder.meteringEnabled = true
        }
    }

    override func viewWillAppear(animated: Bool) {
        // Error 10: overriding conventions: Failure to call super of a built-in method like this one
        super.viewWillAppear(animated)
        stopButton.hidden = true
        recordButton.enabled = true
    }

    @IBAction func recordButton(sender: UIButton) {
        recordButton.enabled = false
        stopButton.hidden = false
        recordingLabel.hidden = false

        audioRecorder.record()
    }

    func audioRecorderDidFinishRecording(recorder: AVAudioRecorder!, successfully flag: Bool) {
        if flag {
            recordedAudio = RecordedAudio()
            recordedAudio.filePathUrl = recorder.url
            recordedAudio.title = recorder.url.lastPathComponent
            // Error 11: sender should be the actual initiating VC
            self.performSegueWithIdentifier(stopRecordingSegue, sender: self)
        }
        else {
            println("Recording was not successful") // Error 12: Grammar
            recordButton.enabled = true
            stopButton.hidden = true
        }
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Error 13: Not using a defined constant string, you've messed up capitalization
        if segue.identifier == stopRecordingSegue {
            let playSoundsVC = segue.destinationViewController as! PlaySoundsViewController
            // REST OF FIX OF MAJOR ERROR 2: send in data to a method so runtime initialization code can run
            playSoundsVC.prepareAudio(recordedAudio)
        }
    }

    @IBAction func stopButton(sender: UIButton) {
        recordButton.enabled = true
        recordingLabel.hidden = true
        audioRecorder.stop()
        AVAudioSession.sharedInstance().setActive(false, error: nil)
    }
}

class RecordedAudio: NSObject{
    var filePathUrl: NSURL!
    var title: String!
}