这个堆栈卡死是一个典型的 主线程同步 XPC 调用死锁 问题。让我详细分析原因和解决方案:
问题原因分析
1. 核心死锁原因
主线程 → XPC 同步调用 → 等待回复 → 但XPC服务需要主线程处理 → 死锁
堆栈显示:
- 第8行:
___NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ - 第14行:
-[AVAudioSession overrideOutputAudioPort:error:] - 第15行:在
handleDevicePluggedIn的 block 中调用
2. 具体死锁场景
// 可能的死锁流程:
1. 主线程执行 overrideOutputAudioPort: 同步调用
2. 通过 XPC 向音频服务发送请求
3. 音频服务需要主线程处理某些回调或事件
4. 但主线程正在等待XPC回复,无法处理音频服务的请求
5. 形成循环等待,导致死锁
解决方案
方案1:改为异步调用(推荐)
// 避免在主线程进行同步XPC调用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
// 如果需要UI更新,回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(@"音频设置失败: %@", error);
} else {
// 更新UI状态
}
});
});
方案2:添加超时机制
// 使用dispatch_semaphore添加超时
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&blockError];
dispatch_semaphore_signal(semaphore);
});
// 设置3秒超时
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
// 超时处理
NSLog(@"音频设置超时,跳过操作");
return;
}
方案3:优化音频会话使用时机
// 避免在音频路由变化通知中立即修改设置
- (void)handleDevicePluggedIn:(NSNotification *)notification {
// 延迟执行,避免冲突
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self safeOverrideAudioPort];
});
}
- (void)safeOverrideAudioPort {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
});
}
方案4:检查音频会话状态
// 在执行操作前检查会话状态
- (void)safeAudioOperation {
AVAudioSession *session = [AVAudioSession sharedInstance];
// 检查是否已激活
if (!session.isInputAvailable) {
NSLog(@"音频会话未就绪,延迟操作");
[self performSelector:@selector(safeAudioOperation) withObject:nil afterDelay:0.5];
return;
}
// 在后台线程执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
[session overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
});
}
预防措施
1. 音频操作最佳实践
// 统一的音频管理类
@interface AudioManager : NSObject
+ (void)safePerformAudioOperation:(void (^)(AVAudioSession *session))operation;
@end
@implementation AudioManager
+ (void)safePerformAudioOperation:(void (^)(AVAudioSession *session))operation {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 确保音频会话已激活
AVAudioSession *session = [AVAudioSession sharedInstance];
if (!session.isInputAvailable) {
// 重试机制
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self safePerformAudioOperation:operation];
});
return;
}
// 执行操作
operation(session);
});
}
@end
// 使用方式
[AudioManager safePerformAudioOperation:^(AVAudioSession *session) {
NSError *error = nil;
[session overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
}];
2. 监控和日志
// 添加详细的错误处理和日志
NSError *error = nil;
BOOL success = [[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
if (!success) {
NSLog(@"音频操作失败: %@", error);
// 上报崩溃分析或用户反馈
[self reportAudioError:error];
}
总结
根本解决原则:
- 绝不在主线程进行同步XPC调用
- 所有音频操作应在后台线程执行
- 添加超时机制和错误处理
- 实现重试逻辑应对临时故障
这个死锁问题通过将音频操作移至后台线程通常可以完全解决。