移除AVAudioSession的outputVolume观察者时出现崩溃如何解决
崩溃原因
- 注册观察者时类型转换导致的对象不匹配:你添加观察者时传入的是
self as! NSObject,如果当前类不是NSObject子类(或者强转后生成了新的桥接对象),移除时直接传入self的话,系统会识别为两个完全不同的观察者对象,认为你要移除的观察者从未注册过,直接触发崩溃。 - 未做注册状态校验:如果
listenVolumeButton方法中audioSession.setActive执行报错,后续添加观察者的代码根本没有执行,你直接调用移除方法也会触发该崩溃;另外重复调用移除方法也会触发同样的报错。 - 原
observeValue方法存在逻辑错误:你把音量减小的判断嵌套在了音量增大的判断分支内部,会导致音量下键的逻辑永远无法触发。
修复方案
方案1:修复传统KVO写法
- 首先新增属性记录观察者注册状态:
// 标记观察者是否已注册 private var isVolumeObserverRegistered = false // 存储上一次的音量值 private var audioLevel: Float = 0.0
- 修改注册方法,移除不必要的强转,添加注册状态判断:
func listenVolumeButton(){ // 避免重复注册 guard !isVolumeObserverRegistered else { return } do { let audioSession = AVAudioSession.sharedInstance() try audioSession.setActive(true, options: []) // UIViewController等系统类本身就是NSObject子类,无需强转 audioSession.addObserver(self, forKeyPath: "outputVolume", options: .new, context: nil) audioLevel = audioSession.outputVolume isVolumeObserverRegistered = true print("初始音量:\(audioLevel)") } catch { print("音频会话激活失败:\(error)") } }
- 修复监听回调的逻辑错误:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { // 不属于当前监听的事件交给父类处理 guard keyPath == "outputVolume", let audioSession = object as? AVAudioSession else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } let newVolume = audioSession.outputVolume if newVolume > audioLevel { print("音量上键被按下") } else if newVolume < audioLevel { print("音量下键被按下") } audioLevel = newVolume print("当前音量:\(newVolume)") }
- 修改移除方法,添加状态校验:
func stopListenVolumeButton() { // 只有已注册的情况下才执行移除 guard isVolumeObserverRegistered else { return } let audioSession = AVAudioSession.sharedInstance() audioSession.removeObserver(self, forKeyPath: "outputVolume") isVolumeObserverRegistered = false }
方案2:使用iOS11+支持的新式KVO(更推荐,避免手动移除崩溃)
基于闭包的新式KVO不需要手动管理观察者注册状态,系统会自动处理生命周期,不会出现移除未注册观察者的崩溃:
// 保存KVO观察对象 private var volumeObservation: NSKeyValueObservation? private var audioLevel: Float = 0.0 // 注册监听 func listenVolumeButton(){ guard volumeObservation == nil else { return } do { let audioSession = AVAudioSession.sharedInstance() try audioSession.setActive(true, options: []) audioLevel = audioSession.outputVolume volumeObservation = audioSession.observe(\.outputVolume, options: .new) { [weak self] _, change in guard let self = self, let newVolume = change.newValue else { return } if newVolume > self.audioLevel { print("音量上键被按下") } else if newVolume < self.audioLevel { print("音量下键被按下") } self.audioLevel = newVolume } } catch { print("音频会话激活失败:\(error)") } } // 停止监听 func stopListenVolumeButton() { volumeObservation?.invalidate() volumeObservation = nil }
内容的提问来源于stack exchange,提问作者Pratik
相关产品推荐
相关产品推荐

