AVPlayer的addPeriodicTimeObserver函数执行异常及设计咨询
问题解答
1. 为什么闭包内添加简单语句就会导致AVPlayer无法播放、应用卡死?
核心原因是触发了无限循环,完全占用了主线程资源,具体逻辑链如下:
- 当你在
addPeriodicTimeObserver的闭包里更新self.doubleValue时,会触发PlayerSlider中doubleValue的didSet方法 - 在
didSet里,你调用了mediaManager.player?.seek(to:),这个操作会立即修改AVPlayer的当前播放时间 - AVPlayer的时间变化又会触发
addPeriodicTimeObserver的闭包再次执行,再次更新doubleValue,形成无限循环 - 这个循环在主线程持续执行,会彻底阻塞主线程,导致应用卡死,音频播放的逻辑自然也无法正常运行
另外还有几个加剧问题的细节:
- 代码中大量使用强制解包(比如
mediaManager.player!、currentItem!.duration),如果player或currentItem为nil,会直接崩溃,还隐藏了潜在的空值风险 - 在
isEnabled的didSet里重复注册通知,每次isEnabled变化都会添加新的观察者,导致setupPlayerObserver被多次调用,添加多个时间观察者,进一步加重主线程负担
2. 这种单例+Slider监听的设计是否合理?
整体设计存在不少耦合和资源管理的问题,不算合理,具体问题和改进建议如下:
存在的问题
- 耦合度过高:
PlayerSlider作为UI控件,直接持有MediaManager单例,违反了单一职责原则——UI控件只应该负责UI的展示和交互,不应该直接依赖业务逻辑层的单例,这会导致控件难以复用,也不利于后续的测试和维护 - 单例的副作用:
MediaManager单例的生命周期和应用绑定,容易导致内存泄漏(比如这里Slider和单例之间的隐式强引用循环),而且单例的全局访问会让状态变化难以追踪 - 资源管理混乱:切换音频时,旧的
AVPlayer和时间观察者没有被移除,会造成内存泄漏;通知没有在合适的时机移除,会导致重复触发 - 状态逻辑不清晰:
currentFile的didSet直接触发播放,没有处理暂停、旧资源释放等边界情况
改进建议
- 用依赖注入替代单例直接引用:给
PlayerSlider添加初始化参数,传入MediaManager的实例(或者定义一个播放控制协议,让MediaManager遵守,降低耦合),这样控件不依赖全局单例,更灵活 - 分离UI和业务逻辑:让
PlayerSlider只负责接收进度更新的回调,以及向外部发送用户拖动滑块的seek请求,不直接操作AVPlayer - 正确管理观察者资源:在
PlayerSlider的deinit方法中移除通知观察者和AVPlayer的时间观察者;在MediaManager切换音频时,先停止旧的player,移除旧的观察者,再创建新的player - 优化状态逻辑:在
currentFile的didSet中,先判断是否需要停止当前播放,再初始化新的player,避免资源浪费
内容的提问来源于stack exchange,提问作者Sam Fischer
相关产品推荐
相关产品推荐

