com.apple.NSURLSession-delegate队列EXC_BAD_ACCESS崩溃原因排查
崩溃问题分析结论
该问题不是iOS系统bug,属于业务代码多线程访问冲突导致的可修复问题,堆栈明确指向业务代码执行逻辑错误,具体分析如下:
堆栈信息拆解
崩溃发生在com.apple.NSURLSession-delegate系统后台队列,栈顶直接指向UbiApi.swift第28行的UbiApi.accountsNeeded -= 1逻辑,完全没有停留在系统框架的执行逻辑内,因此可以直接排除是Apple系统底层的问题。
CFNetwork和GCD相关的栈帧只是上层的回调调度逻辑,不是崩溃触发点。
根因分析
- 逃逸闭包野指针风险:
URLSession.shared.dataTask的回调是逃逸闭包,默认会强引用捕获的self,如果请求返回时UbiApi实例已经被释放,闭包再访问self就会触发野指针,抛出EXC_BAD_ACCESS。 - 多线程无保护读写共享资源:所有回调逻辑默认运行在后台并行队列,
UbiApi.accountsNeeded是全局/静态共享属性,多个并发请求的回调同时修改该值时会触发数据竞争,这种问题是概率性触发的,和系统线程调度策略、并发请求量强相关,因此即使长时间没有修改相关代码,在iOS系统更新、用户请求量上涨后也可能集中爆发,这也符合你提到的多个URL请求场景都出现同类崩溃的特征。 - 强制解包潜在风险:代码中
json.expiration!、json.ticket!的强制解包逻辑,在服务端返回字段为空时也会直接触发崩溃,属于需要修复的潜在问题。
修复方案
1. 闭包添加弱引用捕获
给回调闭包添加[weak self]捕获列表,避免实例释放后访问野指针:
URLSession.shared.dataTask(with: urlRequest) { [weak self] (data, res, err) in guard let self = self, let data = data else { return } // 原有逻辑 }
2. 共享资源读写做线程保护
所有共享属性修改、UserDefaults写入、实例方法调用都切到主队列(或自定义串行队列)执行,避免并行读写冲突:
// 解码逻辑可以留在后台队列,修改共享资源前切队列 do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(self.dateFormatter) let json = try decoder.decode(LoginResponse.self, from: data) // 可选绑定替换强制解包 guard let expirationDate = json.expiration, let ticket = json.ticket, let sessionId = json.sessionId else { return } let token = "Ubi_v1 t=" + ticket // 切主队列执行所有共享资源操作 DispatchQueue.main.async { [weak self] in guard let self = self else { return } UserDefaults.standard.setValue(expirationDate, forKey: "r6_token_expiration_\(account)") UserDefaults.standard.setValue(token, forKey: "r6_token_\(account)") UserDefaults.standard.setValue(sessionId, forKey: "r6_token_sessionId_\(account)") UbiApi.accountsNeeded -= 1 self.CheckIfTokensAreDone() } } catch { // 建议添加错误日志上报,方便后续排查问题 }
内容的提问来源于stack exchange,提问作者SwiftCODA
相关产品推荐
相关产品推荐

