是千辛万苦总结出来的关于使用session来做完美的断点下载,希望能帮到各位猿友####
此篇断点下载的特色
1.支持断点下载,这个必须的
2.支持多任务,任务数可以根据实际情况自行设置
3.支持后台下载(session本身就支持,是不是觉得session很强大,加入session的队列吧)
4.支持app意外退出保存断点信息
5.每隔一秒更新一次进度,降低进度条的更新频率
使用到的第三方工具
AFNetworking
FMDB
好了废话也不多说,直接上代码,希望能帮到各位同行,demo我一托管在github,有兴趣的朋友可以看一下,里面会有一些我写这个demo时注意的一些东西,还有一些总结
demo演示
初始化下载单例
//注册通知处理异常情况
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionDownloadAplicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
_lock = [[NSLock alloc] init];
_lock.name = @"ZBHTTPSessionShareTaskDict";
_taskDict = [NSMutableDictionary dictionary];
//后台任务处理
_completionHandlerDictionary = [NSMutableDictionary dictionary];
_maxCount = 3;
_downloadingList = [NSMutableArray array];
_diskFileList = [NSMutableArray array];
//更新文件状态,防止重新运行程序时,上次未下载完成的任务可能会开始下载
[FileModelDbManager updateUnFinishedFileState];
[_diskFileList addObjectsFromArray:[FileModelDbManager getAllDownloadedFile]];
[_downloadingList addObjectsFromArray:[FileModelDbManager getAllNotCompletedFile]];
[ZB_NetWorkShare ZB_NetWorkShare].backSessionCompletionDelegate = self;
下载关键代码
for (FileModel *file in self.downloadingList) {
if (self.taskDict.count < self.maxCount) {
if (file.fileState == FileWillDownload) {
file.fileState = FileDownloading;
}
} else if(self.taskDict.count > self.maxCount){
if (file.fileState == FileDownloading) {
file.fileState = FileStopDownload;
}
} else {
}
if (file.fileState == FileDownloading) {
NSURLSessionTask *task = [self taskForKey:file.fileUrl];
if (!task) {
[self AF_BeginDownloadFileWithFileModel:file];
}
} else {
//此处未异步回掉self.downloadingcout--,不能及时生效,所以更换另外一种方式
NSURLSessionDownloadTask *task = [self taskForKey:file.fileUrl];
if (task) {
[self removeTaskForKey:file.fileUrl];
if (file) {
file.fileState = FileStopDownload;
[FileModelDbManager insertFile:file];
}
[task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
}];
}
}
}
app意外退出断点支持,为了完成这个功能,楼主也是费了9牛二虎之力,其中使用到了信号量,以及重新创建resumedata,重新创建resumedata必须使用xml的参数,否则会造成生成的resumedata文件不正确
NSDictionary *dict = [NSClassFromString(@"__NSCFLocalDownloadTask") getPropertiesDict];
for (NSString *obj in dict.allKeys) {
if ([@"downloadFile" isEqualToString:obj]) {
id propertyValue = [task valueForKeyPath:obj];
NSDictionary *downDict = [[propertyValue class] getPropertiesDict];
for (NSString *downProperty in downDict.allKeys) {
if ([@"path" isEqualToString:downProperty]) {
NSString *temFilePath = [propertyValue valueForKeyPath:downProperty];
NSData *resumeData = [self reCreateResumeDataWithTask:task tmFilePath:temFilePath];
[self handleResumeData:resumeData file:nil];
break;
}
}
break;
}
}
程序退出后台时执行的代码,由于程序退出的时候,仅有短暂的几秒时间app执行任务,而且不会执行异步任务,所以这里用到了信号来取消后台下载任务
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
for (NSURLSessionDownloadTask *task in _taskDict.allValues) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
[task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
dispatch_semaphore_signal(sem);
}];
[self saveResumeDataWithTask:task];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
还剩下关键的一步就可以支持app被kill时的断点下载了,那就是重新生成resumedata
NSMutableDictionary *resumeDataDict = [NSMutableDictionary dictionary];
NSMutableURLRequest *newResumeRequest = [task.currentRequest mutableCopy];
NSData *tmData = [NSData dataWithContentsOfFile:tmFilePath];
[newResumeRequest addValue:[NSString stringWithFormat:@"bytes=%@-",@(tmData.length)] forHTTPHeaderField:@"Range"];
[resumeDataDict setObject:newResumeRequest.URL.absoluteString forKey:@"NSURLSessionDownloadURL"];
NSData *newResumeRequestData = [NSKeyedArchiver archivedDataWithRootObject:newResumeRequest];
NSData *oriData = [NSKeyedArchiver archivedDataWithRootObject:task.originalRequest];
[resumeDataDict setObject:@(tmData.length) forKey:@"NSURLSessionResumeBytesReceived"];
[resumeDataDict setObject:newResumeRequestData forKey:@"NSURLSessionResumeCurrentRequest"];
[resumeDataDict setObject:@(2) forKey:@"NSURLSessionResumeInfoVersion"];
[resumeDataDict setObject:oriData forKey:@"NSURLSessionResumeOriginalRequest"];
[resumeDataDict setObject:[tmFilePath lastPathComponent] forKey:@"NSURLSessionResumeInfoTempFileName"];
return [NSPropertyListSerialization dataWithPropertyList:resumeDataDict format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
相关链接
关于iOS的后台下载和断点续传,说一说自己的理解
ios使用nsurlsession进行下载
iOS中利用NSURLSession进行文件断点下载
NSURLSessionDownloadTask的深度断点续传
苹果官方论坛
源码我已上传至github很方便朋友们下载demo
如果您有好的建议欢迎留言,或者issue我,谢谢!