iOS基于NSURLSessionConfiguration的后台批量下载任务疑问咨询
问题背景
你实现了分片下载逻辑:每次提交20个1-2MB的下载任务,当前批次完成后再添加新的20个请求。但真机后台测试时,出现了应用挂起的日志,下载进度日志停止,部分文件只完成了部分下载,还偶尔碰到Code=2的文件不存在错误。你的核心疑问是:后台能不能在完成一批下载后自动请求更多文件?还是应用挂起后只能继续处理已经提交的任务?
后台日志解读
先看看你遇到的日志情况:
应用挂起相关日志
default 15:56:03.627080 +0800 xyz __nw_socket_service_writes_block_invoke sendmsg(fd 14, 31 bytes): socket has been closed default 15:56:03.671468 +0800 mediaserverd -CMSessionMgr- cmsmHandleApplicationStateChange: CMSession: Client com.xyz with pid '1447' is now Background Suspended. Background entitlement: NO default 15:56:03.671804 +0800 mediaserverd 1067: pid 1447(xyz) default 15:56:03.672156 +0800 mediaserverd 3138: sid:0x1a653, xyz(1447), 'prim' new state = Background Task Suspended default 15:56:03.673578 +0800 locationd Client com.xyz disconnected default 15:56:03.694200 +0800 symptomsd 1447 com.xyz: BackgroundTaskSuspended (most elevated: BackgroundTaskSuspended) default 15:56:03.716198 +0800 symptomsd Entry, display name com.xyz uuid 02184FD5-F48B-3EF7-B2B6-98AB05145DB9 pid 1447 isFront 0
这里明确显示你的应用已经进入**Background Suspended(后台挂起)**状态,此时应用的所有代码执行都会暂停,包括你用来添加新批次任务的逻辑。
常见错误日志
Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory" UserInfo={NSErrorFailingURLKey...}
你提到这个错误和设备资源相关,通常是因为下载的临时文件被系统清理,或者你没有及时将临时文件移动到持久化存储目录导致的。
核心问题解答
直接给你明确结论:应用挂起后,只能继续处理已经提交到后台NSURLSession的任务,无法自动在完成一批后添加新的下载请求。
原因很简单:
- 当应用进入后台挂起状态,系统会暂停应用的所有线程,你的代码完全停止运行——包括你原本打算在当前批次完成后添加新任务的逻辑。
- 后台NSURLSession的任务是由系统接管的,系统会继续完成已经提交的任务,但不会主动唤醒你的应用去添加新任务。只有当当前所有后台任务完成时,系统才会通过
handleEventsForBackgroundURLSession回调唤醒你的应用,这是你唯一能添加新任务的时机。
结合你的代码分析
后台Session配置
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) { backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[[NSBundle mainBundle] bundleIdentifier]]; } else { backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"downloadmanager"]; } backgroundConfiguration.discretionary = YES; backgroundConfiguration.sessionSendsLaunchEvents = YES; self.backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration delegate:self delegateQueue:NSOperationQueue.mainQueue];
这段配置是正确的:sessionSendsLaunchEvents = YES确保任务完成时系统会唤醒应用;不过discretionary = YES会让系统自行决定下载时机,可能导致任务延迟,如果你需要更及时的下载,可以考虑设为NO(但会增加电量消耗)。
任务添加代码
NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSessionDownloadTask *downloadTask; downloadTask = [self.backgroundSession downloadTaskWithRequest:request];
代码本身没问题,但如果是在应用挂起后尝试执行这段代码,根本不会生效——因为此时应用的线程都被暂停了,代码无法运行。
AppDelegate后台回调
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { DownloadManager.shared().backgroundTransferCompletionHandler = completionHandler print("background task called") }
这里你只保存了completionHandler,但没有利用这个回调时机添加新任务。你需要在这个回调里,当确认当前批次任务全部完成后,立即提交下一批20个下载任务,然后再调用completionHandler()。如果不这样做,应用会在回调结束后很快被挂起,无法添加新任务。
解决建议
- 一次性提交所有任务(如果总数不大):如果你的总下载任务数量不是特别多,直接把所有任务都提交到后台Session,让系统自动处理,这样就不用分批了,也避免了后台唤醒的问题。
- 分批下载的正确姿势:如果必须分批,在
handleEventsForBackgroundURLSession回调被唤醒时,先处理当前批次的完成逻辑,然后立即提交下一批任务,同时可以配合UIApplication.beginBackgroundTask来延长应用的后台运行时间,确保新任务能成功提交。 - 修复Code=2错误:在
URLSession:downloadTask:didFinishDownloadingToURL:代理方法中,一定要及时将临时文件移动到你的应用沙盒内的持久化目录(比如Documents或Library/Caches),避免系统清理临时文件导致找不到文件的错误。
内容的提问来源于stack exchange,提问作者Rahul Verma

