UIスライダを実装し音量を調整したい


現在教本の内容を元に拡張機能(UIスライダの音源の音量調整)  
を実装しようとしています。

解決したい課題としては、現在音楽を再生(play()メソッドを実行)してから音量を調整することはできるのですが、  
音楽再生する前にスライダで音量を調整しようとすると「Thread 1: EXC_BAD_ACCESS (code=1,
address=0x48)」というエラーが出てきて処理が止まってしまいます。

半日かけてエラー解決に取り組んだのですが現状の自分の知識では解決の糸口が見えないため  
質問させて頂きました。  
どなたかご教示頂けると幸いです。

コードは下記に転載します。  
足りない情報があれば追記致します。

[code]

    import UIKit
    import AVFoundation
    import MediaPlayer
    
    class ViewController: UIViewController {
    
        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.
        }
    
        func prepareToPlay() -> Bool {
            return true
        }
    
        //シンバルの音源ファイル指定
        let  cymbalPath = Bundle.main.bundleURL.appendingPathComponent("cymbal.mp3")
        //シンバル用のプレイヤーインスタンスを生成
        var cymbalPlayer = AVAudioPlayer()
    
        //ギターの音源ファイル指定
        let  guitarPath = Bundle.main.bundleURL.appendingPathComponent("guitar.mp3")
        //シンバル用のプレイヤーインスタンスを生成
        var guitarPlayer = AVAudioPlayer()
    
        //バック音楽の音源ファイル指定
        let  backMusicPath = Bundle.main.bundleURL.appendingPathComponent("backmusic.mp3")
        //バック音楽のプレイヤーインスタンスを生成
        var backMusicPlayer = AVAudioPlayer()
    
        @IBAction func cymbal(_ sender: Any) {
            soundPlayer(player: &cymbalPlayer, path: cymbalPath, count: 0)
        }
    
        @IBAction func guitar(_ sender: Any) {
                soundPlayer(player: &guitarPlayer, path: guitarPath, count: 1)
            }
    
        @IBAction func play(_ sender: Any) {
            soundPlayer(player: &backMusicPlayer, path: backMusicPath, count: -1)
        }
    
        //    stopボタンがタップされた時の処理
        @IBAction func stop(_ sender: Any) {
               soundPlayer(player: &backMusicPlayer, path: backMusicPath, count: 0)
                backMusicPlayer.stop()
        }
    
        @IBOutlet weak var volume: UISlider!
    
        @IBAction func volumeController(_ sender: UISlider) {
    
            backMusicPlayer.volume = volume.value
        }
    
        //共通のプレイヤー再生処理
        fileprivate func soundPlayer(player:inout AVAudioPlayer,path:URL, count:Int) {
            do {
                player = try AVAudioPlayer(contentsOf: path, fileTypeHint: nil)
                player.play()
            } catch {
                print("エラーが発生しました！")
            }
        }
     }
    
[/code]




現在あなたが掲載されているコードからわかる、最悪のポイントはこの3行です。

[code]

        var cymbalPlayer = AVAudioPlayer()
    
        var guitarPlayer = AVAudioPlayer()
    
        var backMusicPlayer = AVAudioPlayer()
    
[/code]

各文はインスタンスプロパティを宣言する構文で、それを`AVAudioPlayer()`で初期化しています。ところが、引数なしの`AVAudioPlayer.init()`というイニシャライザの挙動は[Appleの`AVAudioPlayer`公式ドキュメント](https://developer.apple.com/documentation/avfoundation/avaudioplayer)では全く触れられていません。

つまり
**`AVAudioPlayer()`と言う構文で作られたインスタンスがどんな動作をするのかについては何の保証もありません**。Appleのドキュメントはお世辞にも十分とは言えませんので、ドキュメント化が不十分な機能を使わざるを得ない場合もあるのですが、少なくとも`AVAudioPlayer`の場合には、
**`AVAudioPlayer()`と言う構文を使ってはいけません**。

大昔は知りませんが、少なくとも4〜5年前からのiOSでは、
**`AVAudioPlayer()`で作られたインスタンスにどんなメソッドを実行させようとしても、必ずアプリがクラッシュする** と言う動作をします。

* * *

修正方法ですが、そもそもが、
**プロパティ宣言の時に適切なインスタンスを作成できないのであれば、無理に変なインスタンスを作らずに`Optional`型を使うべき**
です。そうすれば、コードの他の部分では値が`nil`かそうでないかで、「適切なインスタンスを作成する前」かどうかを判定することができます。

[code]

    class ViewController: UIViewController {
    
        //...(変更のないところ省略)
    
        //シンバルの音源ファイル指定
        let  cymbalPath = Bundle.main.bundleURL.appendingPathComponent("cymbal.mp3")
        //シンバル用のプレイヤー用プロパティ
        var cymbalPlayer: AVAudioPlayer? //<-
    
        //ギターの音源ファイル指定
        let  guitarPath = Bundle.main.bundleURL.appendingPathComponent("guitar.mp3")
        //シンバル用のプレイヤー用プロパティ
        var guitarPlayer: AVAudioPlayer? //<-
    
        //バック音楽の音源ファイル指定
        let  backMusicPath = Bundle.main.bundleURL.appendingPathComponent("backmusic.mp3")
        //バック音楽のプレイヤー用プロパティ
        var backMusicPlayer: AVAudioPlayer? //<-
    
        @IBAction func cymbal(_ sender: Any) {
            soundPlayer(player: &cymbalPlayer, path: cymbalPath, count: 0)
        }
    
        @IBAction func guitar(_ sender: Any) {
            soundPlayer(player: &guitarPlayer, path: guitarPath, count: 1)
        }
    
        @IBAction func play(_ sender: Any) {
            soundPlayer(player: &backMusicPlayer, path: backMusicPath, count: -1)
        }
    
        //    stopボタンがタップされた時の処理
        @IBAction func stop(_ sender: Any) {
            //↓止める時に新しいプレーヤーを割り当てる必要はない
            //soundPlayer(player: &backMusicPlayer, path: backMusicPath, count: 0)
            backMusicPlayer?.stop() //<-
        }
    
        @IBOutlet weak var volume: UISlider!
    
        @IBAction func volumeController(_ sender: UISlider) {
    
            backMusicPlayer?.volume = volume.value //<-
        }
    
        //共通のプレイヤー再生処理
        fileprivate func soundPlayer(player:inout AVAudioPlayer?,path:URL, count:Int) { //<-`AVAudioPlayer`ではなく`AVAudioPlayer?`にしています
            do {
                player = try AVAudioPlayer(contentsOf: path, fileTypeHint: nil)
                player?.play() //<-
            } catch {
                print("エラーが発生しました！", error) //<-エラーを捨ててしまうとデバッグが困難になる
            }
        }
    }
    
[/code]

どのような「教本」をお使いなのかわかりませんが、`AVAudioPlayer()`なんて構文を直接使っておきながら、上記のような注意事項を全く記載されていないのでしたら、その教本の著者はiOS開発についてそれほど経験を積まれていない可能性が強いので、そのつもりで教本を読んでください。

* * *

いろいろ試行錯誤されているようなので、上記の修正だけでは足りないかもしれませんが、まずは必要になるはずの事柄です。これだけではうまくいかないとか言うことがあれば、コメント等でお知らせ下さい。

