前言:在我们做iOS开发的过程中,常常会有下载文件的需求。我们都知道文件的下载在开发中需求比较频繁,有时候也会用到大文件的断点下载的需求
在iOS9中已经废弃了NSURLConnection的使用,用更为简单的NSURLSession来代替。由于文件的下载我们需要写的代码过于频繁,所以就对文件的下载进行了一下封装,来确保我们之后不必去写那些冗余的代码。
我们先看下效果图:
从图中可以看出,我们开始下载,然后我们杀掉程序,重新打开之后发现可以接着上次的进度继续下载。
接下来我们整理下思路
- 首先确定我们要做大文件的下载
- 我们用到NSURLSession完成操作
- NSRLSession完成下载的话,有几种方式,由于我们需要监控文件的进度和实现断点下载,我们就利用代理的方式完成。
- 这里选择的是NSURLSessionDataDelegate这个代理
- 完成接下来的操作
具体的实现步骤:
- 我们利用懒加载的方式创建NSURLSession的对象
-(NSURLSession *)session
{
if(!_session)
{
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
}
return _session;
}
- 接下来我们我创建任务
由于我们要实现的是断点下载,所以利用的是设置请求头的方式,我们先设置好请求头来确定我们需要下载的文件的大小,然后接下来来创建任务
-(NSURLSessionDataTask *)task
{
if(!_task)
{
NSInteger totalLength = [[NSDictionary dictionaryWithContentsOfFile:TotalLengthFilePath][FileName] integerValue];
if(totalLength && FileInterger == totalLength)
{
NSLog(@"文件已经下载过了");
return nil;
}
//创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:FileURL]];
//设置请求头
NSString *range = [NSString stringWithFormat:@"bytes=%zd-", FileInterger];
[request setValue:range forHTTPHeaderField:@"Range"];
//创建data任务
_task = [self.session dataTaskWithRequest:request];
}
return _task;
}
- 由于是封装,所以提供的有下载和暂停的接口,另外,我们在使用下载的时候常常会有在另一个页面拿到这个下载任务的需求,所以我把封装的这个类设计为单例。
//初始化操作
+(instancetype)shareInstance;
//开始下载文件
-(void)xb_download:(NSString *)url progress:(ProgressBlock)progress compeleted:(CompleteBlock)compelete;
//暂停下载文件
-(void)xb_pauseDownload;
- 在开始下载的任务的方法中开始下载,另外因为提供的有进度参数,和完成时候的参数。这里用Block完成回调,先声明 进度和 完成这个Block
/**文件下载的进度*/
@property (nonatomic, copy) ProgressBlock progress;
/**文件完成时候的操作*/
@property (nonatomic, copy) CompleteBlock compelte;
#pragma mark - 下载处理
-(void)xb_download:(NSString *)url progress:(ProgressBlock)progress compeleted:(CompleteBlock)compelete
{
self.url = url;
//启动任务
[self.task resume];
//赋值
self.progress = progress;
self.compelte = compelete;
}
- 接下来就是实现代理,这里讲一下思路,我们写入文件,用流来写入,来确保我们的程序的运存
- 在接收到响应的方法中我们要打开流,处理文件的长度,并将文件的大小存入字典,方便下次打开文件的时候做判断,具体操作
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
//打开流
[self.stream open];
//获得服务器这次请求返回数据的总长度
self.currentLength = [response.allHeaderFields[@"Content-Length"] integerValue] + FileInterger;
//存储总长度
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:TotalLengthFilePath];
if(dict == nil)
{
dict = [NSMutableDictionary dictionary];
}
dict[FileName] = @(self.currentLength);
[dict writeToFile:TotalLengthFilePath atomically:YES];
//接收这个请求,允许接收服务器的数据
completionHandler(NSURLSessionResponseAllow);
}
- 接下来是接受服务器返回的数据,在这里面我们要写入数据,并通过我们定义的Block变量,计算我们的进度。完成BLock的传值操作。
/**接收到服务器返回的数据(这个方法被调用的次数可能会有点多)*/
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
//写入数据
[self.stream write:data.bytes maxLength:data.length];
//目前的下载长度
NSInteger filecurrentLength = [[[NSFileManager defaultManager] attributesOfItemAtPath:CacheFilePath error:nil][NSFileSize] integerValue];
self.progress(1.0 * filecurrentLength / self.currentLength);
}
- 最后一步 ,我们需要对我们的流进行关闭和释放,同时清除下载的任务,完成 成功时Block参数的传递。
/**请求完毕*/
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
//完成调用Block
self.compelte(error, CacheFilePath);
//关闭流
[self.stream close];
self.stream = nil;
//清除任务
self.task = nil;
self.progress = nil;
}
封装的细节:
- 封装的时候咱们存储文件的时候,通过MD5对文件名进行加密,以确保我们文件名唯一
- 要实现程序杀掉之后接下来继续下载,那么就要来用设置请求头来完成了
- 利用流写文件的时候,在文件写入完毕之后,不要忘记关闭并释放流
- 然后接下来就是定义的一些宏
//下载文件所需要的URL
#define FileURL self.url
//文件名(沙盒中的文件名)
#define FileName [self md5:FileURL]
//文件的存放路径(cache)
#define CacheFilePath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:FileName]
//存储文件总长度的存放路径(cache)
#define TotalLengthFilePath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"totalLength.plist"]
//文件已经下载的长度
#define FileInterger [[[NSFileManager defaultManager] attributesOfItemAtPath:CacheFilePath error:nil][NSFileSize] integerValue]
- 实现单例
#pragma mark - 单例的初始化操作
+(instancetype)shareInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manger = [[self alloc] init];
});
return _manger;
}
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manger = [super allocWithZone:zone];
});
return _manger;
}
文件的下载操作,我们平时用到的也不少,但是网上相关的资源比较的少,由于思路比较的简单,具体操作也不是很麻烦,所以就对文件的下载操作进行了封装,大家可以看着借鉴下