如何有序串行执行NSURLSessionDataTask并追踪下载进度?
你当前的代码无法实现串行下载的核心原因是:NSURLSessionDataTask 的 resume 方法是异步执行的——你用串行队列只是按顺序调用了启动任务的代码,但每个任务启动后会立刻在NSURLSession的后台队列中并行执行,串行队列无法控制任务的完成顺序。
要实现基于代理回调的串行DataTask下载,核心思路是维护一个待下载任务的队列,在当前任务的didCompleteWithError:代理方法中启动下一个任务。这样既能保证串行执行,又能通过代理方法实时更新下载进度。
具体实现代码
首先在你的类中添加必要属性:
@interface YourViewController () <NSURLSessionDataDelegate> @property (nonatomic, strong) NSURLSession *session; @property (nonatomic, strong) NSMutableArray<NSURL *> *downloadURLs; // 待下载URL队列 @property (nonatomic, strong) NSURLSessionDataTask *currentTask; // 当前执行任务 @property (nonatomic, strong) NSMutableData *receivedData; // 当前任务接收的数据 @property (nonatomic, weak) IBOutlet UIProgressView *progressView; @property (nonatomic, weak) IBOutlet UILabel *statusLabel; @end
初始化NSURLSession和待下载队列:
- (void)viewDidLoad { [super viewDidLoad]; // 配置Session,指定代理队列为主队列(方便直接更新UI) NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]]; // 初始化待下载URL列表 self.downloadURLs = [NSMutableArray arrayWithObjects: [NSURL URLWithString:@"https://example.com/file.zip"], [NSURL URLWithString:@"https://example.com/file.z01"], [NSURL URLWithString:@"https://example.com/file.z02"], nil]; }
实现启动下载和任务调度的方法:
- (void)download:(id)sender { [self testInternetConnection]; // 启动第一个任务 [self startNextDownloadTask]; } // 启动队列中的下一个任务 - (void)startNextDownloadTask { if (self.downloadURLs.count == 0) { self.statusLabel.text = @"所有文件下载完成"; self.progressView.progress = 0.0; return; } NSURL *nextURL = self.downloadURLs.firstObject; [self.downloadURLs removeObjectAtIndex:0]; self.statusLabel.text = [NSString stringWithFormat:@"正在下载: %@", nextURL.lastPathComponent]; self.receivedData = [NSMutableData data]; // 创建并启动当前任务 self.currentTask = [self.session dataTaskWithURL:nextURL]; [self.currentTask resume]; }
实现NSURLSession代理方法,处理进度更新和任务完成:
// 接收到数据时更新进度 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self.receivedData appendData:data]; // 计算并更新进度 NSInteger totalBytes = dataTask.response.expectedContentLength; if (totalBytes != NSURLResponseUnknownLength) { float progress = (float)self.receivedData.length / totalBytes; self.progressView.progress = progress; } } // 当前任务完成后启动下一个任务 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (error) { self.statusLabel.text = [NSString stringWithFormat:@"下载失败: %@", error.localizedDescription]; } else { // 将下载完成的数据写入文件 NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:task.response.URL.lastPathComponent]; [self.receivedData writeToFile:filePath atomically:YES]; self.statusLabel.text = [NSString stringWithFormat:@"下载完成: %@", task.response.URL.lastPathComponent]; } // 可选:延迟1秒让用户看到完成状态,再启动下一个任务 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self startNextDownloadTask]; }); }
关键说明
- 用
downloadURLs数组维护待下载任务,每次启动任务时取出第一个URL并移除,严格保证串行顺序 - 代理队列使用主队列,可直接在代理方法中更新UI(进度条、状态标签)
- 在
didCompleteWithError:中触发下一个任务,确保当前任务完全结束(无论成功或失败)后才开始下一个 - 若需要处理下载失败的重试逻辑,可在
didCompleteWithError:中将失败的URL重新加入队列尾部
内容的提问来源于stack exchange,提问作者Edi
相关产品推荐
相关产品推荐

