NSURLSession和NSURLConnection大文件斷點續(xù)傳、后臺切換、取消下載

廢話不說,上效果圖:


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

下載任務(wù)的創(chuàng)建,因為需要斷點續(xù)傳所以設(shè)置請求頭,并且調(diào)用停止任務(wù)的方法[self.downloadTask suspend],點擊下載時,需要每次調(diào)用createDownTask方法判斷需要從哪里開始下載,已經(jīng)下載好的無需再次下載。

// 創(chuàng)建任務(wù)
- (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];
    //設(shè)置請求頭
    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;
    }
}
// 開始/暫停任務(wù)
- (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會一直下載,需要手動關(guān)閉
- (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.downloadTask suspend];
}

關(guān)閉按鈕,點擊取消下載任務(wù)。思路是講內(nèi)存中保存的文件刪去。至于關(guān)閉按鈕什么時候點擊,什么時候不能點擊,可以根據(jù)自己的實際情況進行選擇。我的demo是只要沒有下載完或者還沒有開始下載就可以進行點擊進行取消。[self.session invalidateAndCancel];方法進行取消,注意在實際中invalidateAndCancel需要對session再進行創(chuàng)建才能再下載。

// 手動關(guān)閉下載任務(wù),清除內(nèi)存
- (void)closeClick:(UIButton *)sender{
    [self.session invalidateAndCancel];
    UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"溫馨提示" message:@"確定取消任務(wù)?" 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.接收到服務(wù)器的響應(yīng)就會調(diào)用
 *
 *  @param response   響應(yīng)
 */
#pragma mark
#pragma make - 判斷是否已經(jīng)下載過
- (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.當接收到服務(wù)器返回的實體數(shù)據(jù)時調(diào)用(具體內(nèi)容,這個方法可能會被調(diào)用多次)
 *
 *  @param data       這次返回的數(shù)據(jù)
 */
- (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.加載完畢后調(diào)用(服務(wù)器的數(shù)據(jù)已經(jīng)完全返回后)
 */

- (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中的文件保存由句柄改成數(shù)據(jù)流方法。

總結(jié)

可以在下載頁面開始時展示之前下載的進度,思路是在viewDidAppear或者viewWillAppear方法里面將文件大小從內(nèi)存中讀出,并且總大小保存在內(nèi)存中。此外對于大文件的多線程的下載可以分成多個線程放到不同隊列中下載。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容