NSURLSession和NSURLConnection大文件断点续传、后台切换、取消下载

废话不说,上效果图:


Untitled1.gif

部分一:NSURLSession


@interface sessionViewController ()<NSURLSessionDataDelegate>
// 进度条
@property (nonatomic,strong)UIProgressView *progressView;

// 下载百分比显示
@property (nonatomic,strong)UILabel *textLabel;

// 下载按钮
@property (nonatomic,strong)UIButton *btn;

// 取消按钮
@property (nonatomic,strong)UIButton *closeBtn;

@property (nonatomic,strong)NSURLSessionDataTask *downloadTask;

@property (nonatomic,strong)NSURLSession *session;

// 文件句柄
@property (nonatomic,strong)NSFileHandle *filehandle;

// 大文件的总大小
@property (nonatomic,assign)double totalLenght;

// 已下载的文件大小
@property (nonatomic,assign)double currentLenght;

界面的布局

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    // 界面
    UIProgressView *progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(20, 100, 300, 5)];
    progressView.tintColor = [UIColor redColor];
    progressView.trackTintColor = [UIColor lightGrayColor];
    self.progressView = progressView;
    [self.view addSubview:progressView];
    UIButton *closeBtn = [[UIButton alloc] initWithFrame:CGRectMake(330, 90, 20, 20)];
    [closeBtn setImage:[UIImage imageNamed:@"close"] forState:UIControlStateNormal];
    [closeBtn addTarget:self action:@selector(closeClick:) forControlEvents:UIControlEventTouchUpInside];
    self.closeBtn = closeBtn;
    self.closeBtn.enabled = NO;
    [self.view addSubview:closeBtn];
    
    UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 130, 100, 20)];
    textLabel.font = [UIFont fontWithName:@"American Typewriter" size:24];
    self.textLabel = textLabel;
    [self.view addSubview:textLabel];
    
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(150, 200, 50, 50)];
    [btn setImage:[UIImage imageNamed:@"start"] forState:UIControlStateNormal];
    [btn setImage:[UIImage imageNamed:@"stop"] forState:UIControlStateSelected];
    [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    self.btn = btn;
    [self.view addSubview:btn];
}

效果:


Snip20170524_7.png

下载任务的创建,因为需要断点续传所以设置请求头,并且调用停止任务的方法[self.downloadTask suspend],点击下载时,需要每次调用createDownTask方法判断需要从哪里开始下载,已经下载好的无需再次下载。

// 创建任务
- (void)createDownTask{
    NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];// 默认配置
    NSURLSession *session = [NSURLSession sessionWithConfiguration:cfg delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    self.session = session;

    NSURL *downURL = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:downURL];
    //设置请求头
    NSString *cache =  CACHE;
    NSString *fileName = [cache stringByAppendingPathComponent:DOWNNAME];
    NSString *range = [NSString stringWithFormat:@"bytes=%zd-",[[[NSFileManager defaultManager] attributesOfItemAtPath:fileName error:nil][NSFileSize] intValue]];
    [request setValue:range forHTTPHeaderField:@"Range"];
    NSURLSessionDataTask *downloadTask = [self.session dataTaskWithRequest:request];
    if(self.downloadTask){
        self.downloadTask = nil;
    }
    self.downloadTask = downloadTask;
    if ([[[NSFileManager defaultManager] attributesOfItemAtPath:fileName error:nil][NSFileSize] intValue] != 0) {
        self.closeBtn.enabled = YES;
    }
}
// 开始/暂停任务
- (void)btnClick:(UIButton *)btn{
    btn.selected = !btn.isSelected;
    if (btn.isSelected) {
        self.closeBtn.enabled = YES;
        [self createDownTask];
        [self.downloadTask resume];
    }else {
        [self.downloadTask suspend];
    }
}
// 默认下session会一直下载,需要手动关闭
- (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.downloadTask suspend];
}

关闭按钮,点击取消下载任务。思路是讲内存中保存的文件删去。至于关闭按钮什么时候点击,什么时候不能点击,可以根据自己的实际情况进行选择。我的demo是只要没有下载完或者还没有开始下载就可以进行点击进行取消。[self.session invalidateAndCancel];方法进行取消,注意在实际中invalidateAndCancel需要对session再进行创建才能再下载。

// 手动关闭下载任务,清除内存
- (void)closeClick:(UIButton *)sender{
    [self.session invalidateAndCancel];
    UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"确定取消任务?" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *alertA = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        self.progressView.progress = 0.00;
        self.textLabel.text = @"0.00%";
        
        NSString *cache =  CACHE;
        NSString *fileName = [cache stringByAppendingPathComponent:DOWNNAME];
        NSFileManager *mgr = [NSFileManager defaultManager];
        [mgr removeItemAtPath:fileName error:nil];
        self.closeBtn.enabled = NO;
        self.btn.selected = NO;
    }];
    
    UIAlertAction *alertB = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        [alertC dismissViewControllerAnimated:YES completion:nil];
        _btn.selected = NO;
        [self btnClick:self.btn];
    }];
    [alertC addAction:alertA];
    [alertC addAction:alertB];
    
    [self presentViewController:alertC animated:YES completion:nil];
}

NSURLSession代理方法,用文件的大小是否一样判断是否需要下载,用句柄进行文件保存

/**
 *  1.接收到服务器的响应就会调用
 *
 *  @param response   响应
 */
#pragma mark
#pragma make - 判断是否已经下载过
- (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler{
    
    NSFileManager *mgr = [NSFileManager defaultManager];
    NSString *cache = CACHE;
    NSString *fileName = [cache stringByAppendingPathComponent:DOWNNAME];
    self.currentLenght = [[mgr attributesOfItemAtPath:fileName error:nil][NSFileSize] doubleValue];
    // 下载文件的总长度
    self.totalLenght = (double)response.expectedContentLength + self.currentLenght;
    if (![mgr fileExistsAtPath:fileName]) {
        [mgr createDirectoryAtPath:cache withIntermediateDirectories:YES attributes:nil error:nil];
        [mgr createFileAtPath:fileName contents:nil attributes:nil];
    }else if([[mgr attributesOfItemAtPath:fileName error:nil][NSFileSize] doubleValue] == response.expectedContentLength + self.currentLenght){
        self.progressView.progress = 1.0;
        self.textLabel.text = @"1.00%";
        completionHandler(NSURLSessionResponseCancel);
        [self createAlterView];
        return;
    }
    // 文件的句柄
    self.filehandle = [NSFileHandle fileHandleForUpdatingAtPath:fileName];
    self.progressView.progress = self.currentLenght / self.totalLenght;
    self.textLabel.text = [NSString stringWithFormat:@"%.2f%%",self.progressView.progress];
    completionHandler(NSURLSessionResponseAllow);
}

/**
 *  2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
 *
 *  @param data       这次返回的数据
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    // 句柄移动到文件的末尾
    [self.filehandle seekToEndOfFile];
    // 写入文件
    [self.filehandle writeData:data];
    // 累计长度
    self.currentLenght += data.length;
    
    // 显示进度
    self.progressView.progress = self.currentLenght / self.totalLenght;
    self.textLabel.text = [NSString stringWithFormat:@"%.2f%%",self.currentLenght / self.totalLenght];
}

/**
 *  3.加载完毕后调用(服务器的数据已经完全返回后)
 */

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    [self.filehandle closeFile];
    self.filehandle = nil;
    [self createAlterView];
}


// 提示出下载文件的大小和存储的位置
- (void)createAlterView{
    self.btn.selected = NO;
    self.closeBtn.enabled = NO;
    NSString *cache = CACHE;
    NSString *fileName = [cache stringByAppendingPathComponent:DOWNNAME];
    NSFileManager *mgr = [NSFileManager defaultManager];
    NSDictionary<NSFileAttributeKey, id> *dict = [mgr attributesOfItemAtPath:fileName error:nil];
    NSInteger fileSize = [dict[NSFileSize] doubleValue] / 1024.0 / 1024.0;
    NSString *fileSizeString = [NSString stringWithFormat:@"%.2ld",(long)fileSize];
    if ([dict[NSFileSize] doubleValue] == self.totalLenght) {
        NSString *message = [NSString stringWithFormat:@"文件大小:%@M 文件地址:%@",fileSizeString,fileName];
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"下载完成" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *sureBtn = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            [self dismissViewControllerAnimated:YES completion:nil];
        }];
        // 下载文件的属性
        [alertController addAction:sureBtn];
        [self presentViewController:alertController animated:YES completion:nil];
    }
}

至于NSURLConnection,我就不就行阐述了,思路一样。感兴趣的朋友可以看github中的WMDownTask,其中NSURLConnection中的文件保存由句柄改成数据流方法。

总结

可以在下载页面开始时展示之前下载的进度,思路是在viewDidAppear或者viewWillAppear方法里面将文件大小从内存中读出,并且总大小保存在内存中。此外对于大文件的多线程的下载可以分成多个线程放到不同队列中下载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容