一、基础知识

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释放句柄和指针

红色的箭头表示句柄,灰色的箭头表示移动的路径

iOS 获取下载的bundle_iOS 获取下载的bundle

 

四、程序实现的流程

iOS 获取下载的bundle_#pragma_02

 

五、代码实例

#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