011-文件下载
一、基础知识
1.对NSURLSession的认识
NSURLSession是苹果在iOS7推出的一个类,它具备了NSURLConnection所具备的方法,同时也比它更强大。苹果推出它的目的也就是为了取代NSURLConnection
2.NSURLSession的作用
实现对文件的下载与上传。在NSURLSession中,任何请求都可以被看做是一个任务。而NSURLSessionData有两个子类:NSURLSessionDownloadTask实现文件下载和NSURLSessionUpdateTask实现文件上传
3.NSURLSession的获取
NSURLSession的获取可以用NSURLSessionDownloadTaskdelegate的方法获取,但是必须遵循这个协议。获取如下:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSOperationQueue *queue = [NSOperationQueue mainQueue]; self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:queue];
4.下载任务的创建
NSURL *url = [NSURL URLWithString:@“下载地址”];
self.downLoadTask = [self.session downloadTaskWithURL:url];
5.NSURLSessionDownloadDelegate的代理方法
// 1.当接收到下载数据的时候调用,可以在该方法中监听文件下载的进度(该方法会被调用多次) // 参数1: bytesWritten 本次下载的文件数据大小 // 参数2: totalBytesWritten 已经写入到文件中的数据大小 // 参数3: totalBytesExpectedToWrite 目前文件的总大小 - (void)URLSession:(NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { self.progressView.progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite; } // 2.恢复下载的时候调用该方法 - (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { } // 3.下载完成之后调用该方法 - (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location { NSLog(@"location = %@", location); } // 4.请求完成之后调用(如果error有值就说明下载失败) - (void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { if (error) { NSLog(@"下载失败!"); } else { NSLog(@"下载成功!"); } }
二、对程序几个属性的说明
1.resumeData
该参数包含了继续下载文件的位置信息。也就是说,当你下载了10M的文件数据,暂停了。那么你下次继续下载的时候是从第10M这个位置开始的,而不是从文件最开始的位置开始下载。银儿为了保存这些信息,所以才定义了这个NSData类型的属性:resumeData
2.task
该参数的类型是NSURLSessionDownloadTask。因为在程序调用暂停(pause)这个方法时,必须拥有这个属性,怎么拿到它了?最好的办法就是让控制器拥有这个属性
3.session
该参数的类型是NSURLSession。在程序调用继续下载(resume)这个方法时,必须拥有session。因为之前的任务task被取消了,无法再复用了,所以用懒加载的方法,让session只创建一次,同时 也让控制器拥有了这个属性
三、文件断点下载的思路
实现下载数据分段写入缓存中,防止内存溢出
1)使用NSURLSessionDelegate以及代理方法
2)在成功获取响应的代理方法中,获取沙盒路径,并在该路径下创建空文件和文件句柄
3)在获取data的代理方法中,先设置句柄在沙盒全路径文件末尾,然后通过句柄写入data数据
4)在文件下载完的代理方法中,关闭句柄同时设置句柄引用为nil释放句柄和指针
红色的箭头表示句柄,灰色的箭头表示移动的路径

四、程序实现的流程

五、代码实例
#import "ViewController.h" #define DownLoadFileOneURLString @"http://120.25.226.186:32812/resources/videos/minion_03.mp4" #define DownLoadFileTwoURLString @"http://filelx.liqucn.com/upload/2011/gps/baidumap_AndroidPhone_v8.3.0_8.3.0.1_1006817j.ptada" @interface ViewController ()<NSURLSessionDownloadDelegate> // 下载任务 @property (nonatomic, strong) NSURLSessionDownloadTask *task; // 记录上次暂停下载返回的记录 @property (nonatomic, strong) NSData *resumeData; // 下载会话 @property (nonatomic, strong) NSURLSession *session; // 下载/暂停按钮 @property (nonatomic, strong) UIButton *downLoadBtn; // 进度条 @property (nonatomic, strong) UIProgressView *progressView; // 进度label @property (nonatomic, strong) UILabel *progressLabel; @end @implementation ViewController #pragma mark - 懒加载 // 1.session - (NSURLSession *)session { if (!_session) { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSOperationQueue *queue = [NSOperationQueue mainQueue]; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:queue]; } return _session; } - (void)viewDidLoad { [super viewDidLoad]; [self initUI]; // 界面 } #pragma mark - 界面 - (void)initUI { self.view.backgroundColor = [UIColor whiteColor]; // downLoadBtn CGFloat downLoadBtnW = 80; CGFloat downLoadBtnH = 35; CGFloat downLoadBtnX = (self.view.bounds.size.width - downLoadBtnW) / 2.0; CGFloat downLoadBtnY = 200; self.downLoadBtn = [UIButton buttonWithType:UIButtonTypeCustom]; self.downLoadBtn.frame = CGRectMake(downLoadBtnX, downLoadBtnY, downLoadBtnW, downLoadBtnH); [self.downLoadBtn setTitle:@"下载" forState:UIControlStateNormal]; [self.downLoadBtn setTitle:@"暂停" forState:UIControlStateSelected]; self.downLoadBtn.tintColor = [UIColor whiteColor]; self.downLoadBtn.backgroundColor = [UIColor blackColor]; self.downLoadBtn.titleLabel.font = [UIFont systemFontOfSize:18]; [self.downLoadBtn addTarget:self action:@selector(downLoad:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.downLoadBtn]; // progressView CGFloat progressViewW = 300; CGFloat progressViewH = 40; CGFloat progressViewX = (self.view.bounds.size.width - progressViewW) / 2.0; CGFloat progressViewY = 100; self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(progressViewX, progressViewY, progressViewW, progressViewH)]; self.progressView.progress = 0; [self.view addSubview:self.progressView]; // progressLabel CGFloat progressLabelW = self.progressView.bounds.size.width; CGFloat progressLabelH = progressViewH; CGFloat progressLabelX = self.progressView.frame.origin.x; CGFloat progressLabelY = self.progressView.frame.origin.y - progressLabelH; self.progressLabel = [[UILabel alloc] initWithFrame:CGRectMake(progressLabelX, progressLabelY, progressLabelW, progressLabelH)]; self.progressLabel.text = [NSString stringWithFormat:@"%.2f", self.progressView.progress]; self.progressLabel.font = [UIFont systemFontOfSize:16]; self.progressLabel.textColor = [UIColor redColor]; self.progressLabel.textAlignment = NSTextAlignmentCenter; [self.view addSubview:self.progressLabel]; } #pragma mark - 按钮点击事件 // 下载按钮 - (void)downLoad:(UIButton *)sender { // 修改状态 sender.selected = !sender.isSelected; // 判断下载任务是否为空(为空表示当前是未下载状态,不为空表示当前正在下载) if (self.task == nil) { // 判断记录上次暂停下载返回的记录是否为空(为空表示新的下载,不为空表示已经下载过) if (self.resumeData == nil) { [self start]; // 开始下载 } else { [self resume]; // 恢复下载 } } else { [self pause]; // 暂停下载 } } #pragma mark - 下载任务 // 1.开始下载 - (void)start { // 1.创建下载任务 NSURL *url = [NSURL URLWithString:DownLoadFileOneURLString]; self.task = [self.session downloadTaskWithURL:url]; // 2.开始下载任务 [self.task resume]; } // 2.恢复下载 - (void)resume { // 1.创建下载任务 self.task = [self.session downloadTaskWithResumeData:self.resumeData]; // 2.恢复下载任务 [self.task resume]; // 3.记录上次暂停下载返回的记录置为nil self.resumeData = nil; } // 3.暂停下载 - (void)pause { __weak ViewController *weakSelf = self; [self.task cancelByProducingResumeData:^(NSData * _Nullable resumeData) { // 1.当前暂停下载返回的记录 weakSelf.resumeData = resumeData; // 2.将下载任务置为空 weakSelf.task = nil; }]; } #pragma mark - 协议方法<NSURLSessionDownloadDelegate> // 1.当接收到下载数据的时候调用,可以在该方法中监听文件下载的进度(该方法会被调用多次) // 参数1: bytesWritten 本次下载的文件数据大小 // 参数2: totalBytesWritten 已经写入到文件中的数据大小 // 参数3: totalBytesExpectedToWrite 目前文件的总大小 - (void)URLSession:(NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { self.progressView.progress = (double)totalBytesWritten / totalBytesExpectedToWrite; self.progressLabel.text = [NSString stringWithFormat:@"%.2f", self.progressView.progress]; } // 2.恢复下载的时候调用该方法 - (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { } // 3.下载完成之后调用该方法 - (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"亲,你的文件下载好了,快去看看吧!" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancalAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]; UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { self.progressView.progress = 0; self.progressLabel.text = @"当前没有下载任务"; }]; [alertController addAction:cancalAction]; [alertController addAction:sureAction]; [self presentViewController:alertController animated:YES completion:nil]; // 1.拿到caches文件夹的路径 NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; // 2.拿到绝对路径 NSString *filePath = [cachesPath stringByAppendingPathComponent:downloadTask.response.suggestedFilename]; // 3.移动下载好的文件到指定文件夹 NSFileManager *fileManager = [NSFileManager defaultManager]; [fileManager moveItemAtPath:location.path toPath:filePath error:nil]; // 4.这个时候下载好的文件其实在caches缓存文件夹中,我们可以将它移动到documents中 // ... } // 4.请求完成之后调用(如果error有值就说明下载失败) - (void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { if (error) { NSLog(@"下载失败!"); } else { NSLog(@"下载成功!"); } } @end

浙公网安备 33010602011771号