使用NSURLSession后台上传文件时didCompleteWithError回调task参数为nil求助
我来帮你捋捋这个iOS 11后台上传遇到的task为nil的问题——我之前也碰到过类似的情况,尤其是在iOS 11这个版本里,后台NSURLSession的一些行为确实有点“反直觉”。结合你的描述,给你几个针对性的解决思路:
核心原因:后台Session的任务生命周期由系统托管
后台NSURLSession的任务是交给系统来管理的,哪怕你的App处于后台甚至被临时唤醒,系统都可能会回收原来的task实例,导致URLSession:task:didCompleteWithError:回调里拿到的task参数为nil。但之前的URLSession:dataTask:didReceiveData:回调触发时,任务还在活跃状态,所以task实例还存在。
具体解决办法
1. 不要依赖回调传入的task,通过Session主动获取任务
在完成回调里,放弃直接使用传入的task,转而调用session的getTasksWithCompletionHandler:方法获取当前所有任务,再通过提前保存的taskIdentifier匹配到对应的上传任务:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { // 不要直接用传入的task,大概率为nil [session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { // 遍历上传任务列表,匹配你之前保存的taskIdentifier for (NSURLSessionUploadTask *targetTask in uploadTasks) { if (targetTask.taskIdentifier == self.savedUploadTaskIdentifier) { // 在这里处理任务完成的逻辑,比如检查error、更新上传状态等 NSLog(@"任务完成,ID:%ld", (long)targetTask.taskIdentifier); break; } } }]; }
⚠️ 注意:创建上传任务时,一定要把task.taskIdentifier保存到本地(比如属性、UserDefaults),这样后续才能准确匹配到对应的任务。
2. 检查后台Session的配置与AppDelegate实现
iOS 11对后台Session的配置要求很严格,稍有不慎就会出现异常:
- 确保后台Session是用
backgroundSessionConfigurationWithIdentifier:创建的,且标识符唯一,不要重复创建相同ID的Session(否则系统会复用旧Session,引发状态混乱)。 - 必须在AppDelegate中正确实现
application:handleEventsForBackgroundURLSession:completionHandler:方法,这个方法是系统唤醒App处理后台任务完成事件的关键:
@property (nonatomic, copy) void (^backgroundSessionCompletionHandler)(void); - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { // 保存completionHandler,等所有任务处理完再调用 self.backgroundSessionCompletionHandler = completionHandler; // 获取对应的后台Session NSURLSession *session = [self getBackgroundSessionWithIdentifier:identifier]; if (session) { // 刷新任务状态,确认所有任务都处理完毕后再通知系统 [session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { if (dataTasks.count == 0 && uploadTasks.count == 0 && downloadTasks.count == 0) { if (self.backgroundSessionCompletionHandler) { void (^handlerCopy)(void) = self.backgroundSessionCompletionHandler; self.backgroundSessionCompletionHandler = nil; handlerCopy(); } } }]; } } // 封装获取后台Session的方法 - (NSURLSession *)getBackgroundSessionWithIdentifier:(NSString *)identifier { static NSMutableDictionary *sessionDict; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sessionDict = [NSMutableDictionary dictionary]; }); NSURLSession *session = sessionDict[identifier]; if (!session) { NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier]; session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; sessionDict[identifier] = session; } return session; }
3. 确保任务实例的引用被正确持有
虽然你提到didReceiveData:里task不为nil,但还是要检查代码中是否在任务完成前持有了task的强引用(比如把任务加到一个全局数组里)。如果没有持有,系统可能会提前释放task实例,导致后续回调中无法获取到有效实例。
总结
iOS 11的后台NSURLSession确实存在一些任务回调的小bug,但核心解决方案就是放弃依赖回调传入的task参数,通过Session主动获取任务并匹配,同时保证后台Session的配置和AppDelegate的相关方法正确实现,这样就能解决task为nil的问题了。
内容的提问来源于stack exchange,提问作者adienthu

