iOS的文件下载,涉及到很深的多线程的管理,本文设计的下载逻辑,在多线程方面没有进行细致的管理,以后会完善。
前言
既然是以组件的思路去设计这个功能,首先提供给外界的方法一定要是易用的,我这里是定义了两个类,AWYDownloadHelper(负责下载功能的实现),AWYDownloadManager(负责管理下载的helper),还有二个工具类,分别是文件操作,MD5加密(NSString的分类,负责把NSString对象转成MD5的String)。
下载地址
事件传递可以选择的方式有很多种,本文选择了block的方式。
整体流程
- 下载文件在整体的层面上,分为下载完成,和下载中的文件。这里通过在文件是否放在cache或者temp中来区分下载完成和未完成。
#define cachePath NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject
#define tempPath NSTemporaryDirectory()
self.fileCachePath = [cachePath stringByAppendingPathComponent:url.lastPathComponent];
self.fileTempPath = [tempPath stringByAppendingPathComponent:url.absoluteString];
- 下载的核心代码:
-(void)downloadWithURL:(NSURL *)url{
// [self timer];
//建立两个文件夹,temp存到一个文件夹,full存到一个文件夹
self.fileCachePath = [cachePath stringByAppendingPathComponent:url.lastPathComponent];
self.fileTempPath = [tempPath stringByAppendingPathComponent:[url.absoluteString MD5String]];
//1 首先判断本地是否已经下载好,有的话直接return
if ([AWYDownloadFileManager isFileExist:self.fileCachePath]) {
self.state = downLoadStateCompleteSuccess;
return;
}
if (self.task && [self.task.originalRequest.URL isEqual:url]) {
[self resumeTask];
}
//2 本地没有下载完成,查看临时下载大小,在代理方法中进行比较,这么做的原因是避免了多次发送请求,这里只要拿到header信息就可以。
_tempFileSize = [AWYDownloadFileManager fileSize:self.fileTempPath];
[self downloadWithURL:url offset:_tempFileSize];
//3 设置session的代理,分别实现两个方法,第一次收到请求返回(可以得到header去查看里边的range或者content) 下载随时返回的方法。
}
-(void)downloadWithURL:(NSURL *)url offset:(long long)fileSize{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:[NSString stringWithFormat:@"bytes=%lld-",fileSize] forHTTPHeaderField:@"Range"];
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request];
self.task = task;
[self resumeTask];
}
//提供给manager使用的带有block参数的方法。
-(void)downloadWithURL:(NSURL *)url downInfo:(downLoadInfo)info downloadState:(downLoadStateChange)change progress:(downLoadProgressChange)progress success:(downLoadSuccess)success error:(downLoadFailed)failed{
self.downLoadInfo = info;
self.stateChange = change;
self.progressChange = progress;
self.downLoadSuccess = success;
self.downloadFailed = failed;
[self downloadWithURL:url];
}
//NSURLSesiion的代理方法,这里涉及到下载文件的读写,用到的是NSOutputStream。
- (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler{
NSHTTPURLResponse *hResponse = (NSHTTPURLResponse *)response;
_totalFileSize = [hResponse.allHeaderFields[@"Content-Length"] longLongValue];
if (hResponse.allHeaderFields[@"Content-Range"]) {
_totalFileSize = [[hResponse.allHeaderFields[@"Content-Range"] componentsSeparatedByString:@"/"].lastObject longLongValue];
}
if (self.downLoadInfo) {
self.downLoadInfo(_totalFileSize);
}
if (_tempFileSize > _totalFileSize) {
[AWYDownloadFileManager removeFile:self.fileTempPath];
completionHandler(NSURLSessionResponseCancel);
[self downloadWithURL:response.URL offset:0];
self.state = downLoadStateCompleteFailure;
return;
}
if (_tempFileSize == _totalFileSize) {
[AWYDownloadFileManager moveFile:self.fileTempPath toNewPath:self.fileCachePath];
completionHandler(NSURLSessionResponseCancel);
self.state = downLoadStateCompleteSuccess;
return;
}
self.stream = [NSOutputStream outputStreamToFileAtPath:self.fileTempPath append:YES];
[self.stream open];
completionHandler(NSURLSessionResponseAllow);
}
-(void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data{
_tempFileSize += data.length;
self.progress = _tempFileSize/_totalFileSize * 1.0;
if (self.progressChange) {
self.progressChange(_progress);
}
[self.stream write:data.bytes maxLength:data.length];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
if (error) {
if (error.code == -999) {
self.state = downLoadStatePause;
}else{
self.state = downLoadStateCompleteFailure;
if (self.downloadFailed) {
self.downloadFailed();
}
}
}else{
self.state = downLoadStateCompleteSuccess;
[AWYDownloadFileManager moveFile:_fileTempPath toNewPath:_fileCachePath];
}
}