You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

移除AVAudioSession的outputVolume观察者时出现崩溃如何解决

崩溃原因

  • 注册观察者时类型转换导致的对象不匹配:你添加观察者时传入的是self as! NSObject,如果当前类不是NSObject子类(或者强转后生成了新的桥接对象),移除时直接传入self的话,系统会识别为两个完全不同的观察者对象,认为你要移除的观察者从未注册过,直接触发崩溃。
  • 未做注册状态校验:如果listenVolumeButton方法中audioSession.setActive执行报错,后续添加观察者的代码根本没有执行,你直接调用移除方法也会触发该崩溃;另外重复调用移除方法也会触发同样的报错。
  • 原observeValue方法存在逻辑错误:你把音量减小的判断嵌套在了音量增大的判断分支内部,会导致音量下键的逻辑永远无法触发。

修复方案

方案1:修复传统KVO写法

  1. 首先新增属性记录观察者注册状态:
// 标记观察者是否已注册
private var isVolumeObserverRegistered = false
// 存储上一次的音量值
private var audioLevel: Float = 0.0
  1. 修改注册方法,移除不必要的强转,添加注册状态判断:
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)")
    }
}
  1. 修复监听回调的逻辑错误:
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)")
}
  1. 修改移除方法,添加状态校验:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 17:48:04