

現在教本の内容を元に拡張機能(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]


CC BY-SA 4.0
2019-07-12T03:11:42.920
56582
2022-03-06T05:07:40.697
2019-07-14T02:08:31.843
35081
35081
question
swift4
UIスライダを実装し音量を調整したい


現在あなたが掲載されているコードからわかる、最悪のポイントはこの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開発についてそれほど経験を積まれていない可能性が強いので、そのつもりで教本を読んでください。

* * *

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


CC BY-SA 4.0
2019-07-14T06:34:52.263
56633
2019-07-14T06:34:52.263
13972
56582
answer


※以下、「質問者さんは、こういう誤った理解をしている」という表現をしますが、それに対して、質問者さんがまったく心当たりのない印象を受けるかもしれません。それはおそらく、質問者さんがテキスト（教科書）にしている教本の執筆者が、誤った理解をしているからだということになります。なので、そこらへんは、柔軟に表現を読み替えていただくようお願いします。

質問者さんがおそらくしているであろう、誤った理解は、次の2点が挙げられます。ひとつは、クラス`AVAudioPlayer`に対して。もうひとつは、Swiftならびにオブジェクト指向における、参照型に関するものです。まず、`AVAudioPlayer`クラスに対する誤解についてお話しします。

* * *

`AVAudioPlayer`という名称から、このクラスは、CDデッキや、Walkman、iPod
touchといった携帯音楽プレイヤーのメタファを連想するのではないでしょうか？CDデッキに、ベートーベンの交響曲第九を挿入して、再生ボタンを押したら、第九が再生され、モーツァルトの魔笛のCDを挿入したら、魔笛が再生されるというものです。再生時の音量（Volume）は、CDデッキに設定したものであって、CDを変えるたびに、音量が元に戻ってしまうということは起こりません。  
しかし、`AVAudioPlayer`のインスタンスは、そういうものではなく、一つの曲、一つの音声データにつき、ひとつの`AVAudioPlayer`インスタンスが作られます。  
[Class
AVAudioPlayer](https://developer.apple.com/documentation/avfoundation/avaudioplayer)のプロパティ`url`ならびに`data`の項をみてください。`url`については、

[code]

    var url: URL? { get }
    
[/code]

となっています（`data`も同様）。`{ get }`は、読み出し専用で、書き込み不可のプロパティであることを意味します。（書き込み可能な場合は`{
get set }`という表記となります）  
すなわち、CDを交換したら（URL、ファイルパスを変えたら）別の曲が再生されるということではないということがわかります。イニシアライザ`init(contentsOf
url: URL)`でURLを指定して、いったんインスタンスを生成したら、後から変更できません。

* * *

ご提示のコードからは、Swiftならびにオブジェクト指向におけるインスタンスの参照について、正しく理解されているとは思えません。いい機会ですから、Swiftの「クラスという参照型」について、しっかり勉強しましょう。  
まず、Swiftでは、参照型のオブジェクトはクラスだけに限定されます。そのほかの、構造体（Structure）、列挙体（Enumeration）などは、参照型でなく値型です。  
では、参照型ってなんなの？ですが、まず、以下のコードを、XcodeのPlaygroundで実行しみてください。

[code]

    // クラスHogeの定義
    class Hoge {
        var name: String
    
        var volume: Float = 0
    
        init(name: String) {
            self.name = name
        }
    }
    
    // クラスHogeのインスタンス化。
    var hoge1 = Hoge(name: "Taro")
    hoge1.volume = 100
    print(hoge1.volume) // 100
    
    var hoge0 = hoge1 // hoge1の参照を、hoge0に代入。
    hoge0.volume = 200
    print(hoge1.volume) // 200
    // 変数hoge0、hoge1いずれにも、同一のインスタンスの参照が代入されている。
    
[/code]

変数`hoge0`に対する操作（変更）が、変数`hoge1`に反映される、これが参照型のなせる技なのですが、これだけでは、「だからなんなの？」と思われるかもしれません。しかし、`ViewControllerA`の画面で加えた、ユーザの入力が、`ViewControllerB`の画面のオブジェクト（例えばテーブルとか、コレクションとか）に反映されるメカニズムが、このクラスの参照型で実現できると考えると、使い勝手のあるツールということができます。  
ところが、このメカニズムは同時に、あるクラスのインスタンスの参照を代入していた変数に対して、別のクラスのインスタンスの参照を代入すると、代入前のクラスのインスタンスは参照しなくなるということになります。

[code]

    class Hoge {
        var name: String
    
        var volume: Float = 0
    
        init(name: String) {
            self.name = name
        }
    }
    
    var hoge1 = Hoge(name: "Taro")
    hoge1.volume = 100
    print(hoge1.volume) // 100
    
    var hoge2 = Hoge(name: "Hanako")
    hoge2.volume = 50
    print(hoge2.volume) // 50
    
    hoge1 = hoge2 // 変数hoge1に、hoge2に代入されている、name="Hanako"のインスタンスの参照を代入
    print(hoge1.name) // Hanako
    print(hoge1.volume) // 50
    
[/code]

以上の試行（あるいは実験）から、ご提示のコードは、意図どおりにはまったく動作しないことが、明らかになったと思います。

* * *

以下、クラスの参照型に関する話から離れて、プログラミングの実践的なアドバイスを、おまけで書いておこうと思います。  
まず、`UIViewController`のサブクラスの、初期化処理は、`viewDidLoad()`メソッドに書いてください。そして、プロパティ定義において、デフォルト値を書いて、初期化処理をしたような気になることはやめましょう。  
次に、Xcodeのヘルプとして閲覧できる各リファレンスをひととおり見渡して、これから使おうとしているメソッド類より適したものがないか、確認を怠らないようにしましょう。  
以下のコードを参考にしてください。

[code]

    import UIKit
    import AVFoundation
    
    class ViewController: UIViewController, AVAudioPlayerDelegate {
    
        @IBOutlet weak var playButton: UIButton!
        @IBOutlet weak var volumeSlider: UISlider!
    
        var cymbalPlayer: AVAudioPlayer?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            if let url = Bundle.main.url(forResource: "cymbal", withExtension: "mp3") { // リソース内が、階層化で複雑になっていなければ、リソースファイルをダイレクトに指名できる。
                cymbalPlayer = try? AVAudioPlayer(contentsOf: url) // 例外処理の簡易パターンを使用。
                cymbalPlayer?.delegate = self
                if let value = cymbalPlayer?.volume {
                    volumeSlider.value = value
                }
            }
        }
    
        @IBAction func playCymbal(_ sender: UIButton) {
            cymbalPlayer?.play() // 初期化処理がちゃんとできていれば、たった1行で再生ができます。
            sender.isEnabled = false // 再生中、ボタンを押せないようにしておく。
        }
    
        // cymbalPlayer delegate 再生が終了したら、ボタンを押せるように戻す。
        func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
            playButton.isEnabled = true
        }
    
        // 音量調整
        @IBAction func volumeLebel(_ sender: UISlider) {
            cymbalPlayer?.volume = sender.value
        }
    }
    
[/code]

`AVAudioPlayer`の音源は、たとえばネットワークでダウンロードしたデータや、ミュージックアプリの連携で参照できるファイルのように、やや信頼性の低いものを対象にするため、初期化メソッドが、例外処理を伴うもの（`throws`）になっていますが、アプリのリソースにある音源なら、例外が発生する確率はたいへん低いので、`do
- try - catch`構文でなく、簡易構文である`try?`をここでは使っています。（絶対例外は起きないという前提の`try!`構文もあります）  
`try?`構文は、例外処理を、Optional Chaining、Optional Bindingに置き換える働きをします。


CC BY-SA 4.0
2019-07-16T01:20:23.400
56669
2019-07-16T01:20:23.400
18540
56582
answer