iOS開發(fā)下載、斷點(diǎn)續(xù)傳-NSURLConnection、NSURLSession

最近在研究NSULRSession,順道總結(jié)了NSURLConnection與NSULRSession區(qū)別與聯(lián)系,僅供交流學(xué)習(xí),歡迎各位大神指正。

NSURLConnection

NSURLConnection指的是一組構(gòu)成Foundation框架中URL加載系統(tǒng)的相互關(guān)聯(lián)的組件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache。

創(chuàng)建connection
    // 1.URL
    NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];
    
    //2.請(qǐng)求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //設(shè)置請(qǐng)求頭
    NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
    [request setValue:range forHTTPHeaderField:@"Range"];
    
    //3.下載
    self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

這里用到了代理,遵守NSURLConnectionDataDelegate 協(xié)議.

下面是代理方法
// 請(qǐng)求失敗時(shí)調(diào)用(請(qǐng)求超時(shí)、網(wǎng)絡(luò)異常)
 -(void)connection:(NSURLConnectio**)connection didFailWithError:(NSError *)error{
}

// 1.接收到服務(wù)器的響應(yīng)就會(huì)調(diào)用
   -(void)connection:(NSURLConnection**)connection didReceiveResponse:(NSURLResponse *)response{
}

// 2.當(dāng)接收到服務(wù)器返回的實(shí)體數(shù)據(jù)時(shí)調(diào)用(具體內(nèi)容,這個(gè)方法可能會(huì)被調(diào)用多次)
   -(void)connection:(NSURLConnection**)connection didReceiveData:(NSData *)data{
}

// 3.加載完畢后調(diào)用(服務(wù)器的數(shù)據(jù)已經(jīng)完全返回后)
   -(void)connectionDidFinishLoading:(NSURLConnection *)connection{
}

通過didReceiveData這個(gè)代理方法每次傳回來一部分文件,最終我們把每次傳回來的數(shù)據(jù)拼接合并成一個(gè)我們需要的文件寫入沙盒,最終就獲取到了我們需要的數(shù)據(jù),需要注意的在我們獲取一部分data的時(shí)候就寫入沙盒中,然后釋放內(nèi)存中的data,而不是直接用來一個(gè)接受文件的NSMutableData,它一直都在內(nèi)存中,會(huì)隨著文件的下載一直變大。
寫入的時(shí)候這里要用到NSFilehandle這個(gè)類,這個(gè)類可以實(shí)現(xiàn)對(duì)文件的讀取、寫入、更新。在接受到響應(yīng)的時(shí)候就在沙盒中創(chuàng)建一個(gè)空的文件,然后每次接收到數(shù)據(jù)的時(shí)候就拼接到這個(gè)文件的最后面,通過- (unsigned long long)seekToEndOfFile 這個(gè)方法,這樣在下載過程中內(nèi)存的問題就解決了。

屏幕快照 2016-08-31 上午10.55.49.png

斷點(diǎn)下載

暫停/繼續(xù)下載是我們下載中過程中必不可少的的功能了,如果沒有暫停功能,用戶體驗(yàn)相比會(huì)很差,而且實(shí)際場景下如果突然網(wǎng)絡(luò)不好中斷了,沒有實(shí)現(xiàn)斷點(diǎn)下載的話我們只能重新下載了,用戶體驗(yàn)非常不好。
下面我們來了解斷點(diǎn)下載功能。

NSURLConnection 只提供了一個(gè)cancel方法,這并不是暫停,而是取消下載任務(wù)。如果要實(shí)現(xiàn)斷點(diǎn)下載必須要了解HTTP協(xié)議中請(qǐng)求頭的Range,通過設(shè)置請(qǐng)求頭的Range我們可以指定下載的位置、大小。
如果我們這樣設(shè)置bytes=500-,表示從500字節(jié)以后的所有字節(jié),只需要在didReceiveData中記錄已經(jīng)寫入沙盒中文件的大小,把這個(gè)大小設(shè)置到請(qǐng)求頭中,因?yàn)榈谝淮蜗螺d肯定是沒有執(zhí)行過didReceive方法,self.currentLength也就為0,也就是從頭開始下。

代碼如下

-(void)ButtonAction:(UIButton *)sender
{

sender.selected = !sender.selected;

if (sender.selected) {
    
    // 1.URL
    NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];
    
    //2.請(qǐng)求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //設(shè)置請(qǐng)求頭
    NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
    [request setValue:range forHTTPHeaderField:@"Range"];
    
    //3.下載
    self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

} else {
    [self.connection cancel];
    self.connection = nil;
}

}
ps:為了提高下載的效率,我們一般采用多線程下載。

NSURLSession

NSURLSession是iOS7之后新的網(wǎng)絡(luò)接口,NSURLSession也是一組相互依賴的類,而NSURLSession的不同之處在于,它將NSURLConnection替換為 NSURLSession和 NSURLSessionConfiguration,以及3個(gè) NSURLSessionTask
的子類: NSURLSessionDataTask , NSURLSessionUploadTask, 和NSURLSessionDownloadTask。另外,上面的NSURLConnection要自己去控制內(nèi)存寫入相應(yīng)的位置,而NSURLSession則不需要手動(dòng)寫入沙盒,更加方便了我們的使用。

三種任務(wù)類型:
1.NSURLSessionDataTask : 普通的GET\POST請(qǐng)求
2.NSURLSessionDownloadTask : 文件下載3.NSURLSessionUploadTask : 文件上傳(很少用,一般服務(wù)器不支持)

NSURLSession 使用

NSURLSession請(qǐng)求
// 1.得到session對(duì)象
NSURLSession* session = [NSURLSession sharedSession];
NSURL* url = [NSURL URLWithString:@""];

// 2.創(chuàng)建一個(gè)task,任務(wù)
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
    //data返回?cái)?shù)據(jù)
}];

//    [session dataTaskWithRequest:<#(NSURLRequest *)#> completionHandler:<#^(NSData *data, NSURLResponse *response, NSError *error)completionHandler#>]

//3.開始任務(wù)
[dataTask resume];

NSURLSession 下載

使用NSURLSession下載相對(duì)于NSURLConnection就非常簡單了,不需要去手動(dòng)控制邊下載邊寫入沙盒的問題,蘋果都幫我們做好了。

代碼如下
NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];
// 得到session對(duì)象
NSURLSession *session = [NSURLSession sharedSession];

//創(chuàng)建任務(wù)
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    
// location : 臨時(shí)文件的路徑(下載好的文件),也就是下載好的文件寫入沙盒的地址,打印一下發(fā)現(xiàn)下載好的文件被自動(dòng)寫入的temp文件夾下面了。

NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    // response.suggestedFilename : 建議使用的文件名,一般跟服務(wù)器端的文件名一致
 NSString *file = [caches stringByAppendingPathComponent:response.suggestedFilename];
    
    // 將臨時(shí)文件剪切或者復(fù)制Caches文件夾
 NSFileManager *mgr = [NSFileManager defaultManager];
    
    // AtPath : 剪切前的文件路徑
    // ToPath : 剪切后的文件路徑
   [mgr moveItemAtPath:location.path toPath:file error:nil];
}];

 // 開始任務(wù)
[downloadTask resume];

sandbox:/Users/maying/Library/Developer/CoreSimulator/Devices/42CE6C49-4CC6-47A3-8992-B8CABE1A9678/data/Containers/Data/Application/1A6948E7-78AC-478D-9751-E25AC199B359

屏幕快照 2016-08-31 下午1.57.11.png

但是在下載完成之后會(huì)自動(dòng)刪除temp中的文件,所有我們需要做的只是在回調(diào)中把文件移動(dòng)(或者復(fù)制,反正之后會(huì)自動(dòng)刪除)到caches中,也就是上面將臨時(shí)文件剪切或者復(fù)制Caches文件夾的過程。
下載完結(jié)果如下:


屏幕快照 2016-08-31 下午2.01.34.png

ps:通過這種方式下載有個(gè)缺點(diǎn)就是無法監(jiān)聽下載進(jìn)度,要監(jiān)聽下載進(jìn)度,我們通常的作法是通過delegate,而且NSURLSession的創(chuàng)建方式也有所不同。首先遵守協(xié)議<NSURLSessionDownloadDelegate>
協(xié)議里面有三個(gè)方法。

創(chuàng)建任務(wù)如下
// 得到session對(duì)象
 NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration] ;                                    

//默認(rèn)配置
 NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//創(chuàng)建任務(wù) 
 NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url]; 

//開始任務(wù)
 [downloadTask resume];

協(xié)議方法如下

    #pragma mark -- NSURLSessionDownloadDelegate
//1.下載完畢會(huì)調(diào)用 (@param location,文件臨時(shí)地址)
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    
    // response.suggestedFilename:建議使用的文件名,一般跟服務(wù)器端的文件名一致
    NSString *filePath = [cache stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
    //將臨時(shí)文件剪切或復(fù)制到Caches文件夾
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    // AtPath : 剪切前的文件路徑 ,ToPath : 剪切后的文件路徑
    [fileManager moveItemAtPath:location.path toPath:filePath error:nil];
    
    NSLog(@"下載完成");
    
}
//2.執(zhí)行下載任務(wù)時(shí)有數(shù)據(jù)寫入,在這里面監(jiān)聽下載進(jìn)度(totalBytesWritten/totalBytesExpectedToWrite
@param bytesWritten              這次寫入的大小
@param totalBytesWritten         已經(jīng)寫入的大小
@param totalBytesExpectedToWrite 文件總大小)

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
                                      
    self.myProgress.progress = (double)totalBytesWritten/totalBytesExpectedToWrite;

    self.progressDesLabel.text = [NSString stringWithFormat:@"下載進(jìn)度%f:",(double)totalBytesWritten/totalBytesExpectedToWrite];
}

//3.恢復(fù)下載后調(diào)用
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    
}
 NSURLSessionDownloadTask斷點(diǎn)下載

取消任務(wù)

 __weak typeof(self) weakSelf = self;

[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
    //  resumeData : 包含了繼續(xù)下載的開始位置\下載的url
    weakSelf.resumeData = resumeData;
    weakSelf.downloadTask = nil;
}];

ps:需要注意的是Block中循環(huán)引用的問題

取消操作調(diào)用一個(gè)Block回調(diào)后傳入一個(gè)resumeData,該參數(shù)包含了繼續(xù)下載文件的位置信息。也就是說,當(dāng)我們下載了200M的文件數(shù)據(jù),突然暫停了。下次當(dāng)我們進(jìn)來的時(shí)候繼續(xù)下載的是從第200M這個(gè)位置開始的,而不是從文件最開始的位置開始下載。因而為了保存這些信息,所以才定義了resumeData這個(gè)NSData類型的屬性,這個(gè)data包含了url和繼續(xù)下載的位置,也就是已經(jīng)下載數(shù)據(jù)的大小。

通過resumeData來創(chuàng)建任務(wù)的方法
-(NSURLSessionDownloadTask**)downloadTaskWithResumeData:(NSData*)resumeData;

因此,我們要做的就是在取消操作的回調(diào)中記錄好resumeData,然后在恢復(fù)下載的時(shí)候調(diào)用上面的方法創(chuàng)建任務(wù)就好了,相對(duì)NSURLconnection手動(dòng)寫入沙盒方便了不少。需要注意的是下載比較耗費(fèi)資源,我們可以采用多線程分條下載后組成我們需要的文件數(shù)據(jù)。

本文示例demo下載:
本文Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容