天天看點

iOS開發下載下傳、斷點續傳-NSURLConnection、NSURLSession

最近在研究NSULRSession,順道總結了NSURLConnection與NSULRSession差別與聯系,僅供交流學習,歡迎各位大神指正。

##NSURLConnection

NSURLConnection指的是一組構成Foundation架構中URL加載系統的互相關聯的元件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache。

建立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.請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

//設定請求頭
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];

//3.下載下傳
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
           

這裡用到了代理,遵守NSURLConnectionDataDelegate 協定.

下面是代理方法

// 請求失敗時調用(請求逾時、網絡異常)
-(void)connection:(NSURLConnectio**)connection didFailWithError:(NSError *)error{
}

// 1.接收到伺服器的響應就會調用
-(void)connection:(NSURLConnection**)connection didReceiveResponse:(NSURLResponse *)response{
}

// 2.當接收到伺服器傳回的實體資料時調用(具體内容,這個方法可能會被調用多次)
-(void)connection:(NSURLConnection**)connection didReceiveData:(NSData *)data{
}

// 3.加載完畢後調用(伺服器的資料已經完全傳回後)
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
}
           

通過didReceiveData這個代理方法每次傳回來一部分檔案,最終我們把每次傳回來的資料拼接合并成一個我們需要的檔案寫入沙盒,最終就擷取到了我們需要的資料,需要注意的在我們擷取一部分data的時候就寫入沙盒中,然後釋放記憶體中的data,而不是直接用來一個接受檔案的NSMutableData,它一直都在記憶體中,會随着檔案的下載下傳一直變大。

寫入的時候這裡要用到NSFilehandle這個類,這個類可以實作對檔案的讀取、寫入、更新。在接受到響應的時候就在沙盒中建立一個空的檔案,然後每次接收到資料的時候就拼接到這個檔案的最後面,通過- (unsigned long long)seekToEndOfFile 這個方法,這樣在下載下傳過程中記憶體的問題就解決了。

iOS開發下載下傳、斷點續傳-NSURLConnection、NSURLSession

###斷點下載下傳

暫停/繼續下載下傳是我們下載下傳中過程中必不可少的的功能了,如果沒有暫停功能,使用者體驗相比會很差,而且實際場景下如果突然網絡不好中斷了,沒有實作斷點下載下傳的話我們隻能重新下載下傳了,使用者體驗非常不好。

下面我們來了解斷點下載下傳功能。

NSURLConnection 隻提供了一個cancel方法,這并不是暫停,而是取消下載下傳任務。如果要實作斷點下載下傳必須要了解HTTP協定中請求頭的Range,通過設定請求頭的Range我們可以指定下載下傳的位置、大小。

如果我們這樣設定bytes=500-,表示從500位元組以後的所有位元組,隻需要在didReceiveData中記錄已經寫入沙盒中檔案的大小,把這個大小設定到請求頭中,因為第一次下載下傳肯定是沒有執行過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.請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//設定請求頭
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之後新的網絡接口,NSURLSession也是一組互相依賴的類,而NSURLSession的不同之處在于,它将NSURLConnection替換為 NSURLSession和 NSURLSessionConfiguration,以及3個 NSURLSessionTask

的子類: NSURLSessionDataTask , NSURLSessionUploadTask, 和NSURLSessionDownloadTask。另外,上面的NSURLConnection要自己去控制記憶體寫入相應的位置,而NSURLSession則不需要手動寫入沙盒,更加友善了我們的使用。

三種任務類型:

1.NSURLSessionDataTask : 普通的GET\POST請求

2.NSURLSessionDownloadTask : 檔案下載下傳3.NSURLSessionUploadTask : 檔案上傳(很少用,一般伺服器不支援)

####NSURLSession 使用

NSURLSession請求
// 1.得到session對象
NSURLSession* session = [NSURLSession sharedSession];
NSURL* url = [NSURL URLWithString:@""];

// 2.建立一個task,任務
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

//data傳回資料
}];

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

//3.開始任務
[dataTask resume];
           

###NSURLSession 下載下傳

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

代碼如下:

NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];

// 得到session對象
NSURLSession *session = [NSURLSession sharedSession];

//建立任務
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {

// location : 臨時檔案的路徑(下載下傳好的檔案),也就是下載下傳好的檔案寫入沙盒的位址,列印一下發現下載下傳好的檔案被自動寫入的temp檔案夾下面了。

NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// response.suggestedFilename : 建議使用的檔案名,一般跟伺服器端的檔案名一緻
NSString *file = [caches stringByAppendingPathComponent:response.suggestedFilename];

// 将臨時檔案剪切或者複制Caches檔案夾
NSFileManager *mgr = [NSFileManager defaultManager];

// AtPath : 剪切前的檔案路徑
// ToPath : 剪切後的檔案路徑
[mgr moveItemAtPath:location.path toPath:file error:nil];
}];

// 開始任務
[downloadTask resume];
           

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

iOS開發下載下傳、斷點續傳-NSURLConnection、NSURLSession

但是在下載下傳完成之後會自動删除temp中的檔案,所有我們需要做的隻是在回調中把檔案移動(或者複制,反正之後會自動删除)到caches中,也就是上面将臨時檔案剪切或者複制Caches檔案夾的過程。

下載下傳完結果如下:

iOS開發下載下傳、斷點續傳-NSURLConnection、NSURLSession

ps:通過這種方式下載下傳有個缺點就是無法監聽下載下傳進度,要監聽下載下傳進度,我們通常的作法是通過delegate,而且NSURLSession的建立方式也有所不同。首先遵守協定

協定裡面有三個方法。

建立任務如下
// 得到session對象
NSURLSessionConfiguration *configuration =     [NSURLSessionConfiguration defaultSessionConfiguration] ;                                    

//預設配置
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//建立任務 
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url]; 

//開始任務
[downloadTask resume];
           

協定方法如下

#pragma mark -- NSURLSessionDownloadDelegate
//1.下載下傳完畢會調用 (@param location,檔案臨時位址)
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

// response.suggestedFilename:建議使用的檔案名,一般跟伺服器端的檔案名一緻
NSString *filePath = [cache stringByAppendingPathComponent:downloadTask.response.suggestedFilename];

//将臨時檔案剪切或複制到Caches檔案夾
NSFileManager *fileManager = [NSFileManager defaultManager];

// AtPath : 剪切前的檔案路徑 ,ToPath : 剪切後的檔案路徑
[fileManager moveItemAtPath:location.path toPath:filePath error:nil];

NSLog(@"下載下傳完成");
}
//2.執行下載下傳任務時有資料寫入,在這裡面監聽下載下傳進度   (totalBytesWritten/totalBytesExpectedToWrite
 @param bytesWritten              這次寫入的大小
 @param totalBytesWritten         已經寫入的大小
 @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:@"下載下傳進度%f:",(double)totalBytesWritten/totalBytesExpectedToWrite];
}

//3.恢複下載下傳後調用
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{

}
NSURLSessionDownloadTask斷點下載下傳
           

###取消任務

__weak typeof(self) weakSelf = self;

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

ps:需要注意的是Block中循環引用的問題

取消操作調用一個Block回調後傳入一個resumeData,該參數包含了繼續下載下傳檔案的位置資訊。也就是說,當我們下載下傳了200M的檔案資料,突然暫停了。下次當我們進來的時候繼續下載下傳的是從第200M這個位置開始的,而不是從檔案最開始的位置開始下載下傳。因而為了儲存這些資訊,是以才定義了resumeData這個NSData類型的屬性,這個data包含了url和繼續下載下傳的位置,也就是已經下載下傳資料的大小。

通過resumeData來建立任務的方法

-(NSURLSessionDownloadTask**)downloadTaskWithResumeData:(NSData*)resumeData;
           

是以,我們要做的就是在取消操作的回調中記錄好resumeData,然後在恢複下載下傳的時候調用上面的方法建立任務就好了,相對NSURLconnection手動寫入沙盒友善了不少。需要注意的是下載下傳比較耗費資源,我們可以采用多線程分條下載下傳後組成我們需要的檔案資料。

本文示例demo下載下傳