文件断点下载(NSURLSessionDataTask)的使用
来源:互联网 发布:深圳蜂窝网络是培训 编辑:程序博客网 时间:2024/06/13 09:43
之前用afn2.x的AFHttpOperation结合sqlite数据库管理做了文件的断点下载功能,之后苹果宣布要开始限制ipv4,不过AFN的东西时给予high-level的APIs的,因此不需要修改,但是国外的开发者建议使用AFN3.0版本。
闲来无事就想重新集成一下,迁移AFN3.0的时候因为没有了HTTPOperation,所以在修改代码的时候全部用NSURLSessionDowonloadTask代替,不过由于之前的数据库逻辑已经定型,且多处使用,修改起来比较复杂,DownloadTask是先下载临时文件,下载完成后再迁移到指定文件夹,并不能通过range来指定下载位置的起始,如果用户直接杀死App,又需要记住resumeData来重新下载,这样在多线程同时下载多个的时候集成出了问题,可能是我逻辑没有屡通,总觉的这样修改起来比较费力。
最后我完全摒弃了AFN,改而实用系统提供的URLSession和URLSessionDataTask及它的代理方法来实现,这样不需要修改现存的数据库逻辑,只需要修改下载暂停继续这部分的控制。
感谢大神提供:https://github.com/HHuiHao/HSDownloadManager。
首先用一个单例类来管理下载,单例类并不存储下载内容的数据,数据只在函数之间传递。
@interface TTDownloadManager : NSObject/** * 单例 * * @return 返回单例对象 */+ (instancetype)sharedInstance;/** * 开启任务下载资源 * * @param model 下载参数 * @param progressBlock 回调下载进度 * @param stateBlock 下载状态 */- (void)download:(DownloadModel *)model progress:(void(^)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress))progressBlock state:(void(^)(DownloadState state))stateBlock;
model包含下载文件的参数,对应创建的sqlite数据库的表,可以在内重组文件名,从而保证拿到已下载大小的range,重新创建task继续下载,我使用的方法也是居于上面的Git连接修改的,因为要适用自己的项目,其他大概都差不多,大家可以先看看git项目,相信可以满足大家的大部分需求。
在作者的session类中,我也添加了doanloadModel的引用,从而方便在代理方法中拿到文件路径相关的参数做比较及存值。
@property (nonatomic, copy) NSString *fileName;@property (nonatomic,strong)DownloadModel *model;
文笔拙劣,其实写个博客也是想为了给自己留个笔记,方便以后有用的话不需要在翻来翻去。直接贴代码吧,大家可以参考我上面给的git项目地址,作者写的很好。
TTDownloadManager
//// TTDownloadManager.h// DownloadDemo//// Created by qihb on 16/6/3.// Copyright © 2016年 Qihb. All rights reserved.//#import <Foundation/Foundation.h>#import "TTSessionModel.h"#import "DownloadModel.h"@interface TTDownloadManager : NSObject/** * 单例 * * @return 返回单例对象 */+ (instancetype)sharedInstance;/** * 开启任务下载资源 * * @param model 下载参数 * @param progressBlock 回调下载进度 * @param stateBlock 下载状态 */- (void)download:(DownloadModel *)model progress:(void(^)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress))progressBlock state:(void(^)(DownloadState state))stateBlock;/** * 查询该资源的下载进度值 * * @param url 下载地址 * * @return 返回下载进度值 */- (CGFloat)progress:(NSString *)fileName;/** * 获取该资源总大小 * * @param url 下载地址 * * @return 资源总大小 */- (NSInteger)fileTotalLength:(NSString *)url;/** * 判断该资源是否下载完成 * * @param url 下载地址 * * @return YES: 完成 */- (BOOL)isCompletion:(NSString *)url;/** * 删除该资源 * * @param url 下载地址 */- (void)deleteFile:(NSString *)url;/** * 清空所有下载资源 */- (void)deleteAllFile;/** * 暂停所有下载 */-(void)pauseAllTask;/** * 取消下载 * */-(void)cancelTaskWithModel:(DownloadModel *)model;@end
.m
//// TTDownloadManager.m// DownloadDemo//// Created by qihb on 16/6/3.// Copyright © 2016年 Qihb. All rights reserved.//#import "TTDownloadManager.h"#import "NSString+Hash.h"// 缓存主目录#define TTCachesDirectory [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"TTCache"]// 保存文件名#define TTFileKey(url) url.md5String// 文件的存放路径(caches)#define TTFileFullpath(url) [TTCachesDirectory stringByAppendingPathComponent:TTFileName(url)]// 文件的已下载长度#define TTDownloadLength(name) [[[NSFileManager defaultManager] attributesOfItemAtPath:SAVE_MODEL_PATH(name) error:nil][NSFileSize] integerValue]// 存储文件总长度的文件路径(caches)#define TTTotalLengthFullpath [TTCachesDirectory stringByAppendingPathComponent:@"totalLength.plist"]@interface TTDownloadManager ()<NSCopying, NSURLSessionDelegate>/** 保存所有任务(注:用md5后作为key) */@property (nonatomic, strong) NSMutableDictionary *tasks;/** 保存所有下载相关信息 */@property (nonatomic, strong) NSMutableDictionary *sessionModels;@end@implementation TTDownloadManager- (NSMutableDictionary *)tasks{ if (!_tasks) { _tasks = [NSMutableDictionary dictionary]; } return _tasks;}- (NSMutableDictionary *)sessionModels{ if (!_sessionModels) { _sessionModels = [NSMutableDictionary dictionary]; } return _sessionModels;}static TTDownloadManager *_downloadManager;+ (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _downloadManager = [super allocWithZone:zone]; }); return _downloadManager;}- (nonnull id)copyWithZone:(nullable NSZone *)zone{ return _downloadManager;}+ (instancetype)sharedInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _downloadManager = [[self alloc] init]; }); return _downloadManager;}/** * 创建缓存目录文件 */- (void)createCacheDirectory{ NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:TTCachesDirectory]) { [fileManager createDirectoryAtPath:TTCachesDirectory withIntermediateDirectories:YES attributes:nil error:NULL]; }}/** * 开启任务下载资源 */- (void)download:(DownloadModel *)model progress:(void (^)(NSInteger, NSInteger, CGFloat))progressBlock state:(void (^)(DownloadState))stateBlock{ NSString *model_url = model.model_url; NSString *fileName = [NSString stringWithFormat:@"%@.%@",model.model_md5_str,model.model_format]; if (!model_url) return; if ([self isCompletion:fileName]) { stateBlock(DownloadStateCompleted); NSLog(@"----该资源已下载完成"); return; } // 暂停 if ([self.tasks valueForKey:fileName]) { [self handle:fileName]; return; } // 创建缓存目录文件 [self createCacheDirectory]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]]; NSString *toPath = SAVE_MODEL_PATH(fileName); // 创建流 NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:toPath append:YES]; // 创建请求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:model_url]]; // 设置请求头 NSString *range = [NSString stringWithFormat:@"bytes=%zd-", TTDownloadLength(fileName)]; [request setValue:range forHTTPHeaderField:@"Range"]; // 创建一个Data任务 NSURLSessionDataTask *task = [session dataTaskWithRequest:request]; NSTimeInterval time = [[NSDate date] timeIntervalSince1970]; NSUInteger taskIdentifier = (unsigned long)time; [task setValue:@(taskIdentifier) forKeyPath:@"taskIdentifier"]; // 保存任务 [self.tasks setValue:task forKey:fileName]; TTSessionModel *sessionModel = [[TTSessionModel alloc] init]; sessionModel.url = model_url; sessionModel.fileName = fileName; sessionModel.model = model; sessionModel.progressBlock = progressBlock; sessionModel.stateBlock = stateBlock; sessionModel.stream = stream; [self.sessionModels setValue:sessionModel forKey:@(task.taskIdentifier).stringValue]; [self start:fileName];}- (void)handle:(NSString *)key{ NSURLSessionDataTask *task = [self getTask:key]; if (task.state == NSURLSessionTaskStateRunning) { [self pause:key]; } else { [self start:key]; }}/** * 开始下载 */- (void)start:(NSString *)key{ NSURLSessionDataTask *task = [self getTask:key]; [task resume]; [self getSessionModel:task.taskIdentifier].stateBlock(DownloadStateStart);}/** * 暂停下载 */- (void)pause:(NSString *)key{ NSURLSessionDataTask *task = [self getTask:key]; [task suspend]; [self getSessionModel:task.taskIdentifier].stateBlock(DownloadStateSuspended);}/** * 根据url获得对应的下载任务 */- (NSURLSessionDataTask *)getTask:(NSString *)key{ return (NSURLSessionDataTask *)[self.tasks valueForKey:key];}/** * 根据url获取对应的下载信息模型 */- (TTSessionModel *)getSessionModel:(NSUInteger)taskIdentifier{ return (TTSessionModel *)[self.sessionModels valueForKey:@(taskIdentifier).stringValue];}/** * 判断该文件是否下载完成 */- (BOOL)isCompletion:(NSString *)fileName{ if ([self fileTotalLength:fileName] && TTDownloadLength(fileName) == [self fileTotalLength:fileName]) { return YES; } return NO;}/** * 查询该资源的下载进度值 */- (CGFloat)progress:(NSString *)fileName{ return [self fileTotalLength:fileName] == 0 ? 0.0 : 1.0 * TTDownloadLength(fileName) / [self fileTotalLength:fileName];}/** * 获取该资源总大小 */- (NSInteger)fileTotalLength:(NSString *)fileName{ return [[NSDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath][fileName] integerValue];}#pragma mark - 删除/** * 删除该资源 */- (void)deleteFile:(NSString *)fileName{ NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:SAVE_MODEL_PATH(fileName)]) { // 删除沙盒中的资源 [fileManager removeItemAtPath:SAVE_MODEL_PATH(fileName) error:nil]; // 删除任务 [self.tasks removeObjectForKey:fileName]; [self.sessionModels removeObjectForKey:@([self getTask:fileName].taskIdentifier).stringValue]; // 删除资源总长度 if ([fileManager fileExistsAtPath:TTTotalLengthFullpath]) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath]; [dict removeObjectForKey:fileName]; [dict writeToFile:TTTotalLengthFullpath atomically:YES]; } }}/** * 暂停所有下载 */-(void)pauseAllTask{ NSArray *allKeys = [self.tasks allKeys]; if (allKeys&&allKeys.count>0) { for (int i=0; i<allKeys.count; i++) { NSString *key = [allKeys objectAtIndex:i]; NSURLSessionDataTask *task = [self getTask:key]; [task suspend]; TTSessionModel *sessionModel =[self getSessionModel:task.taskIdentifier]; sessionModel.model.model_download_flag = @"2"; [sessionModel.model saveOrUpdate]; } }}/** * 取消下载 * */-(void)cancelTaskWithModel:(DownloadModel *)model{ NSString *key = [NSString stringWithFormat:@"%@.%@",model.model_md5_str,model.model_format]; NSURLSessionDataTask *task = [self getTask:key]; [task cancel]; [self deleteFile:key];}#pragma mark - 代理#pragma mark NSURLSessionDataDelegate/** * 接收到响应 */- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{ TTSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier]; // 打开流 [sessionModel.stream open]; // 获得服务器这次请求 返回数据的总长度 NSInteger totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + TTDownloadLength(sessionModel.fileName); sessionModel.totalLength = totalLength; // 存储总长度 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath]; if (dict == nil) dict = [NSMutableDictionary dictionary]; dict[sessionModel.fileName] = @(totalLength); [dict writeToFile:TTTotalLengthFullpath atomically:YES]; // 接收这个请求,允许接收服务器的数据 completionHandler(NSURLSessionResponseAllow);}/** * 接收到服务器返回的数据 */- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{ TTSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier]; // 写入数据 [sessionModel.stream write:(uint8_t *)data.bytes maxLength:data.length]; // 下载进度 NSUInteger receivedSize = TTDownloadLength(sessionModel.fileName); NSUInteger expectedSize = sessionModel.totalLength; CGFloat progress = 1.0 * receivedSize / expectedSize; sessionModel.progressBlock(receivedSize, expectedSize, progress);}/** * 请求完毕(成功|失败) */- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ TTSessionModel *sessionModel = [self getSessionModel:task.taskIdentifier]; if (!sessionModel) return; if ([self isCompletion:sessionModel.fileName]) { // 下载完成 sessionModel.stateBlock(DownloadStateCompleted); } else if (error){ if (error.code == NSURLErrorTimedOut||error.code == NSURLErrorNetworkConnectionLost) { sessionModel.stateBlock(DownloadStateSuspended); }else{ // 下载失败 sessionModel.stateBlock(DownloadStateUnBegin); } } // 关闭流 [sessionModel.stream close]; sessionModel.stream = nil; // 清除任务 [self.tasks removeObjectForKey:sessionModel.fileName]; [self.sessionModels removeObjectForKey:@(task.taskIdentifier).stringValue];}@end
TTSessionModel
//// TTSessionModel.h// DownloadDemo//// Created by qihb on 16/6/3.// Copyright © 2016年 Qihb. All rights reserved.//#import <Foundation/Foundation.h>#import <UIKit/UIKit.h>#import "DownloadModel.h"typedef enum { /** 未下载 */ DownloadStateUnBegin = 0, /** 下载中 */ DownloadStateStart, /** 下载暂停 */ DownloadStateSuspended, /** 下载完成 */ DownloadStateCompleted,}DownloadState;//0未下载 1正在下载 2暂停下载 3已完成下载@interface TTSessionModel : NSObject/** 流 */@property (nonatomic, strong) NSOutputStream *stream;/** 下载地址 */@property (nonatomic, copy) NSString *url;@property (nonatomic, copy) NSString *fileName;@property (nonatomic,strong)DownloadModel *model;/** 获得服务器这次请求 返回数据的总长度 */@property (nonatomic, assign) NSInteger totalLength;/** 下载进度 */@property (nonatomic, copy) void(^progressBlock)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress);/** 下载状态 */@property (nonatomic, copy) void(^stateBlock)(DownloadState state);@end
DownloadModel
//// DownloadModelList.h// Up Studio//// Created by qihb on 16/5/12.// Copyright © 2016年 gjh. All rights reserved.//#import "DBBaseModel.h"#define dbModelKeyWord @"model_md5_str"@interface DownloadModel : DBBaseModel@property(nonatomic,copy)NSString *model_md5_str; //md5串@property(nonatomic,copy)NSString *model_image_url; //图片远程下载地址@property(nonatomic,copy)NSString *model_url; //模型远程下载地址@property(nonatomic,copy)NSString *model_size; //文件大小@property(nonatomic,copy)NSString *model_name; //名字@property(nonatomic,copy)NSString *model_format; //格式 up3/stl@property(nonatomic,copy)NSString *model_type; //分类@property(nonatomic,copy)NSString *model_image_path; //图片本地路径@property(nonatomic,copy)NSString *model_path; //模型本地路径@property(nonatomic,copy)NSString *model_subModel_num; //子模型数量@property(nonatomic,copy)NSString *model_payyed_flag; //付款标识@property(nonatomic,copy)NSString *model_price; //价格@property(nonatomic,copy)NSString *model_copyright; //版权@property(nonatomic,copy)NSString *model_from_source; //来源 0预设 1下载 2本地保存@property(nonatomic,copy)NSString *model_download_flag; //0未下载 1正在下载 2暂停下载 3已完成下载@property(nonatomic,copy)NSString *model_download_percentage;//下载百分比@property(nonatomic,copy)NSString *recent_time; //最近一次使用时间//获取所有下载完成的模型数据+(NSArray *)findAllDownloadFinished;@end
downloadModel的部分大家可以参照我之前的博客,数据库DataBaseQueue多线程安全的博客,这个就是继承DBBaseModel实现的,也是最近改到项目里的,写代码就是边开发边重构吗,学以致用。
- 文件断点下载(NSURLSessionDataTask)的使用
- ios:NSURLSessionDataTask做文件断点下载
- iOS之网络—— NSURLSessionDataTask文件离线断点下载、NSURLSession文件上传、AFN基本使用、Cocoapods安装
- iOS开发网络篇 一一 NSURLSessionDataTask实现大文件离线断点下载(重点)
- 依靠NSOutputStream,NSURLSession,NSURLSessionDataTask创建一个支持断点下载的任务
- 使用XUtils进行文件的断点下载
- NSURLSessionDataTask使用
- 使用NSURLSessionDataTask进行大文件断点续传
- Service的使用:断点下载
- 使用NSURLConnection实现大文件断点下载
- 使用NSURLConnection实现大文件断点下载
- 使用NSURLConnection实现大文件断点下载
- 大文件的多线程断点下载
- 多线程断点下载文件的需求
- 使用okgo实现文件下载(断点下载)
- J2ME断点下载文件
- 多线程断点下载文件
- 多线程断点下载文件
- sysbenchmark常用测试笔记
- 第九周项目4——-广义表算法库及应用(1)
- mysql悲观锁总结和实践
- 访谈李佳:从机械维修到月入3万的SEO创业经历
- 第9周项目2-对称矩阵压缩存储的实现与应用(1)
- 文件断点下载(NSURLSessionDataTask)的使用
- 与Html5结下一字之缘
- Highcharts 在低版本 IE 上使用注意事项
- 关于使用Caffe在android平台测试的实践
- Linux设置中文字符
- 《APUE》笔记-第三章-文件I/O
- 训练神经网络的五大算法
- 第九周 项目3-稀疏矩阵的三元组表示的实现及应用
- linux java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver