AVFAudio调用performSelector:withObject:finishedPlaying时崩溃排查求助
performSelector:withObject:finishedPlaying崩溃问题 从你给出的崩溃栈来看,崩溃发生在objc_msgSend,且处于__NSThreadPerformPerform上下文——这说明RunLoop在执行你通过performSelector提交的任务时,给一个无效对象发送了消息。这种崩溃90%以上都是野指针问题:要么接收消息的对象已经被释放,要么该对象根本不响应finishedPlaying这个方法。
下面给你一步步的排查和解决思路:
1. 优先确认:接收消息的对象是否已被提前释放?
performSelector如果是延迟执行(比如用afterDelay)或者跨线程投递任务,很容易出现目标对象在任务执行前就被销毁的情况。比如你在音频回调里调用[self performSelector:@selector(finishedPlaying:) withObject:item afterDelay:0.1],结果self在这0.1秒内被销毁,等RunLoop执行任务时,就会给已释放的对象发消息,直接触发崩溃。
排查方法:
- 开启Xcode僵尸对象调试:在Scheme的Diagnostics面板勾选"Zombie Objects",重新运行。崩溃发生时,Xcode会明确告诉你是给哪个类的已释放对象发了消息,还能展示对象被释放的调用栈,直接定位根源。
- 在目标对象的
dealloc方法加日志:比如NSLog(@"XXX对象已释放"),如果崩溃前打印了这条日志,就实锤了对象提前释放的问题。
2. 检查方法签名与Selector拼写
有时候崩溃是因为Selector拼写错误,或者方法参数类型不匹配:
- 你声明的方法是
- (void)finishedPlaying:(AVPlayerItem *)item,但调用时用了@selector(finishedPlaying)(少了冒号); - 传递的参数类型和方法期望的不匹配,比如把
NSString传给了需要AVPlayerItem的参数。
排查方法:
- 用编译器校验:把
@selector(finishedPlaying:)改成NSSelectorFromString(@"finishedPlaying:"),如果编译器警告找不到该方法,就是拼写或声明问题; - 确认方法在头文件中声明,或在
@implementation里正确实现,避免因私有方法导致的Selector不识别。
3. 用Block替代PerformSelector,从根源规避问题
performSelector是较老旧的API,本身存在生命周期和类型安全隐患。现在更推荐用GCD Block替代,不仅更安全,还能清晰管理对象生命周期:
比如跨线程调用:
// 替代performSelector:withObject:onThread: __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { [strongSelf finishedPlaying:yourObject]; } });
比如延迟调用:
// 替代performSelector:withObject:afterDelay: __weak typeof(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { [strongSelf finishedPlaying:yourObject]; } });
Block会自动捕获对象引用,加上weakSelf+strongSelf的组合,既能避免循环引用,又能确保调用时对象仍存活,从根源杜绝野指针崩溃。
4. 注意AVFAudio回调的线程环境
AVFAudio的回调通常运行在专门的音频线程(非主线程)。如果你在这个回调里直接调用performSelector到主线程,一定要确保主线程的对象没有提前释放。另外,不要在音频线程中做UI操作——虽然这不是本次崩溃的直接原因,但也是常见的坑点。
内容的提问来源于stack exchange,提问作者ordinaryman09

