NSURLSessionDataTask偶发崩溃求助:dataTaskWithRequest调用异常
我开发了一款支持异步刷新的天气APP,自己测试从未遇到崩溃,但苹果崩溃报告显示每天约有一次崩溃,且不局限于特定设备。APP用户量不小,每几分钟就会刷新一次,但我不清楚用户的崩溃报告上报比例,没法判断崩溃实际有多罕见。我已经试过一些方案,比如确保创建dataTask的Async Downloader类不被销毁等。目前有两种上报的崩溃类型,最常见的发生在以下代码处:
-(void)startDownload { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:fileURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:12]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; if (!session || !request || ![session respondsToSelector:@selector(dataTaskWithRequest:completionHandler:)]) return; // 堆栈追踪指向下方代码崩溃 self.dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {...} // ... }
上述“防御性”if语句是合理性检查,崩溃堆栈追踪如下:
其中self.dataTask的定义为@property NSURLSessionDataTask *dataTask;。
补充说明:我无法通过Xcode查看崩溃转储的更多信息,因此用文本编辑器打开获取了完整信息:
Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Triggered by Thread: 9 Last Exception Backtrace: 0 CoreFoundation 0x1ca108cb4 __exceptionPreprocess + 164 (NSException.m:202) 1 libobjc.A.dylib 0x1c32243d0 objc_exception_throw + 60 (objc-exception.mm:356) 2 CoreFoundation 0x1ca27dab8 -[NSObject(NSObject) doesNotRecognizeSelector:] + 136 (NSObject.m:140) 3 CoreFoundation 0x1ca11f0e8 ___forwarding___ + 1592 (NSForwarding.m:3578) 4 CoreFoundation 0x1ca185900 _CF_forwarding_prep_0 + 96 (:-1) 5 App 0x1009667cc -[AsyncDownloader startDownload] + 396 (AsyncDownloader.m:79)
触发崩溃的线程信息:
Thread 9 name: Thread 9 Crashed: 0 libsystem_kernel.dylib 0x0000000208743558 __pthread_kill + 8 (:-1) 1 libsystem_pthread.dylib 0x0000000229411118 pthread_kill + 268 (pthread.c:1670) 2 libsystem_c.dylib 0x00000001d162b178 abort + 180 (abort.c:118) 3 libc++abi.dylib 0x000000022934fbf8 abort_message + 132 (:-1) 4 libc++abi.dylib 0x000000022933f444 demangling_terminate_handler() + 348 (:-1) 5 libobjc.A.dylib 0x00000001c3229ea4 _objc_terminate() + 144 (objc-exception.mm:498) 6 libc++abi.dylib 0x000000022934efbc std::__terminate(void (*)()) + 16 (:-1) 7 libc++abi.dylib 0x000000022934ef60 std::terminate() + 56 (:-1) 8 libdispatch.dylib 0x00000001d15caec0 _dispatch_client_callout + 40 (object.m:563) 9 libdispatch.dylib 0x00000001d15ce330 _dispatch_continuation_pop + 504 (queue.c:306) 10 libdispatch.dylib 0x00000001d15e1908 _dispatch_source_invoke + 1588 (source.c:961) 11 libdispatch.dylib 0x00000001d15cde6c _dispatch_queue_override_invoke + 500 (queue.c:0) 12 libdispatch.dylib 0x00000001d15dc944 _dispatch_root_queue_drain + 396 (queue.c:7051) 13 libdispatch.dylib 0x00000001d15dd158 _dispatch_worker_thread2 + 164 (queue.c:7119) 14 libsystem_pthread.dylib 0x000000022940ada0 _pthread_wqthread + 228 (pthread.c:2631) 15 libsystem_pthread.dylib 0x000000022940ab7c start_wqthread + 8 (:-1) Thread 9 crashed with ARM Thread State (64-bit): x0: 0x0000000000000000 x1: 0x0000000000000000 x2: 0x0000000000000000 x3: 0x0000000000000000 x4: 0x0000000229353647 x5: 0x000000016fa2acb0 x6: 0x000000000000006e x7: 0x0000000000002700 x8: 0xc5115eb4a9cc1e34 x9: 0xc5115eb5c66eae34 x10: 0x0000000000000200 x11: 0x000000000000000b x12: 0x000000000000000b x13: 0x00000000001ff800 x14: 0x00000000000007fb x15: 0x000000009002802e x16: 0x0000000000000148 x17: 0x000000016fa2b000 x18: 0x0000000000000000 x19: 0x0000000000000006 x20: 0x000000000001e10f x21: 0x000000016fa2b0e0 x22: 0x0000000000000110 x23: 0x0000000000000000 x24: 0x0000000000000000 x25: 0x000000016fa2b0e0 x26: 0x0000000000030008 x27: 0x0000000282e3d180 x28: 0x0000000220904c80 fp: 0x000000016fa2ac20 lr: 0x0000000229411118 sp: 0x000000016fa2ac00 pc: 0x0000000208743558 cpsr: 0x40001000 esr: 0x56000080 Address size fault
请问有哪些排查方向或尝试方案可以避免该崩溃?整体来看崩溃相当罕见,我怀疑是APP被系统终止导致的异常退出,但仍欢迎任何建议。
从崩溃堆栈来看,核心问题是-[NSObject(NSObject) doesNotRecognizeSelector:],即调用了某个对象不存在的方法。结合代码和堆栈信息,给出以下排查方向和修复方案:
1. 确认self的有效性
崩溃发生在给self.dataTask赋值时,有可能此时self已经被释放,变成野指针(僵尸对象)。即便你已经确保AsyncDownloader不被销毁,异步场景下仍可能出现对象生命周期异常:
- 给
AsyncDownloader的dataTask属性添加nonatomic, strong修饰(未添加的话),保证赋值时内存管理正确。 - 在
startDownload方法开头增加self有效性检查:if (!self) return; - 闭包中使用弱引用+强引用捕获,避免循环引用或野指针:
__weak typeof(self) weakSelf = self; self.dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; // 后续处理逻辑 }];
2. 优化NSURLSession的生命周期
每次调用startDownload都创建新的NSURLSession,不仅可能导致资源泄漏,还可能在极端场景下引发对象状态异常:
- 改为全局复用一个
NSURLSession实例,在AsyncDownloader的初始化方法中创建一次,后续复用。 - 校验
NSURLSession的配置参数,避免因配置异常导致创建出的session对象状态异常。
3. 完善防御性检查逻辑
当前的if检查了session、request和session的方法响应,可进一步细化:
- 提前检查
fileURL是否为空或无效,避免创建无效NSURL:if (!fileURL || fileURL.length == 0) return; NSURL *url = [NSURL URLWithString:fileURL]; if (!url) return; NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:12]; - 赋值
self.dataTask前,取消之前的运行中任务,避免任务叠加导致异常:if (self.dataTask && self.dataTask.state == NSURLSessionTaskStateRunning) { [self.dataTask cancel]; }
4. 验证系统终止导致的异常
你怀疑是系统终止引发崩溃,可从以下方面验证:
- 这类崩溃的
Exception Codes为0x0000000000000000,大概率是系统因内存不足或后台超时终止APP。可在APP中加入内存监控,记录内存使用情况,同时优化后台刷新逻辑:- 限制后台刷新频率,避免短时间内发起过多请求。
- 后台任务完成后及时清理资源,释放内存。
- 使用苹果
MetricKit框架收集性能数据,包括内存使用、后台任务执行时长等,辅助判断是否为系统终止导致的崩溃。
5. 增加日志与监控
为了复现或定位问题,需补充关键节点的日志:
- 在
startDownload方法的开头、关键检查点、赋值dataTask前后添加日志,记录self、session、request的内存地址和状态。 - 接入第三方崩溃监控工具(如Firebase Crashlytics),获取更详细的崩溃上下文,包括设备系统版本、APP版本、崩溃前操作路径等。
内容的提问来源于stack exchange,提问作者Ecuador

