提供的功能
- 通過URL將數據下載到內存
- 通過URL將數據下載到文件系統
- 將數據上傳到指定的URL
- 在後臺完成上述操作(支持後臺網絡操作,除非用戶強行關閉應用程序)
NSURLConnection和NSURLSession的關係示意圖
NSURLSession中的請求都看做一個請求任務(task)。
NSURLSession的Task繼承關係如下:
- NSURLSessionTask 是一個抽象類,提供了一些基本的方法
- NSURLSessionDataTask 是一個具體的 task 類,可以獲取數據
- NSURLSessionDownloadTask 是一個具體的 task 類,可以下載數據
- NSURLSessionUploadTask 是一個具體的 task 類,可以上傳數據
- NSURLSessionStreamTasj 是一個具體的 task 類,以流的方式請求數據,使用較少
每一個類都有對應的協議。
代碼體現
基本使用
- (void)sesssion {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];
// 2. 如果使用 GET 請求,request 可以省略
// session - 蘋果爲了方便程序員的開發,提供了一個全局的 session 單例 (我們是直接獲取的,不需要創建)
NSURLSession *session = [NSURLSession sharedSession];
// 3. 數據任務 - 所有的任務,都是由session 發起的
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// 4.將得到數據進行返序列化
NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]); }];
// 5. 啓動任務(一定要啓動任務)
[task resume];
}
注意以上發起的DataTask任務中:
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- 該方法中會自動將 url 轉換爲一個請求對象(NSURLRequest),並且該請求對象是 GET 請求方式
- 回調方法是在子線程中運行的,所以如果在回調方法中刷新 UI 必須回到主線程中
- 回調方法中有兩個參數 response / error,這兩個參數和 該消息的接受者(即 NSURLSessionDataTask 對象)中的 response / error 是指同一個內容,即地址相同
- 使用該方法的缺點是不能監聽獲取數據的進度,因爲只有當全部請求完數據後,纔會調用這個方法,也就是說,data 中的數據是請求的全部數據.
// @param request 請求對象
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
withRequest方法可以手動設置請求對象,這樣就可以指定GET/POST中的任何一種方法了。而withURL方法只能使用GET方法。
在第三方框架對於 NSURLSession 的基本的封裝思路就是這樣,給一個 url ,後直接啓動就可以了。
- (void)sesssionDemo {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];
// 2. 啓動會話
[self taskWithURL:url];
}
- (void)taskWithURL:(NSURL *)url {
// 1. 全局會話發起數據任務 - 所有的任務,都是由session 發起的
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]); }] resume];
}
但是使用單例類的缺點就是不能實時監控獲取數據的情況。因爲以上兩種方法中只有當請求完全部的數據後纔會調用回調方法,如果想要實時獲取並監控請求進度,就要設置代理方法。
NSURLSession下載進度跟進
下載進度的跟進是通過代理實現的。
需要跟蹤進度,就需要使用代理,代理是一對一的關係,我就要使用一個自定義的會話。
@property(strong, nonatomic)NSURLSession *session;
-
注意delegate Queue如果是傳代理方法在子線程中運行時,則UI的刷新要注意在主線程進行。
-
而主隊列是
[NSOperationQueue mainQueue]
-
如何選擇隊列: 網絡訪問結束後,如果不需要做複雜的操作,可以指定主隊列,這樣不用考慮線程間通訊。
-
由於下載本身是異步的,不會阻塞主線程工作。
-
在主線程中代理的話,所有的代理回調,都是在主線程異步的,不會阻塞主線程執行!(主線程異步並不會開闢新的線程, 只是把隊列任務放到異步執行, 等待主隊列中的當前任務都完成, 主線程空閒的時候, 再來執行需要執行的任務)。
// @param configuration 配置信息對象
// @param delegate 代理對象
// @param queue 代理方法在哪個線程中運行,如果傳 nil 則會在子線程中運行代理方法
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
- (void)sessionDownload {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/123.mp4"];
// 2. 下載
// 進度跟進:代理,蘋果的頭文件中,代理都是定義在後面的
// 注意:如果要跟進下載進度,不能使用全局會話
// 這個方法是和下面那個方法是矛盾的,使用了這個方法下面那個必須實現的代理方法是不能使用的
//
[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
//
// 這個是一個完成的輸出
//
NSLog(@"%@ %@", location.path, [NSThread currentThread]);
//
}] resume];
要想使用下面的代理方法的話:直接使用如下方法下載數據。
// 直接啓動任務,可以通過代理跟蹤進度
[[self.session downloadTaskWithURL:url] resume];}
以下是必須實現的協議方法,表示下載完成了
- location表示文件存儲的路徑
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
};
以下是斷點續傳,可以什麼都不寫,這個是用來實現下載跟進的
在恢復下載的時候可以調用該方法:
- fileOffset表示從什麼地方開始下載
- expectedTotalBytes表示文件的總大小。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
};
以下方法是用來進行下載跟進的 ,下載進度的跟進存在的問題,存在一個峯值的問題。
-
downloadTask 表示下載任務
-
bytesWritten 表示本次寫入的數據大小
-
totalBytesWritten 下載的數據總大小
-
totalBytesExpectedToWrite 文件的總大小
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ // 下載的進度
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
// 打印下載的進度
NSLog(@"progress = %f",progress);
}
NSURLSession的斷點續傳問題
斷點續傳主要考慮的是將會話暫停繼續還是將任務進行暫停繼續。
NSURLSession 提供的斷點續傳存在的問題,不能"停",應用程序不能終止,一旦終止,續傳數據就丟失了,下次還需要重頭再來!
@property (nonatomic, strong) NSURLSession *session;/// 下載任務
@property (nonatomic, strong) NSURLSessionDownloadTask *task;/// 續傳數據
- 啓動下載任務
- (IBAction)start {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
// 發起下載會話
self.task = [self.session downloadTaskWithURL:url];
// 啓動任務
[self.task resume];
}
- 暫停下載任務
/// 暫停 - 下載任務
- (IBAction)pause {
NSLog(@"暫停");
// 使用需要續傳的數據,暫停下載任務
// 暫停方法,只需要執行一次,被暫停就不用再次暫停
/** 1. 所有的任務都是由會話發起的
2. 當任務啓動後,會話會對任務進行強引用
3. 一旦任務被取消,意味着會話不再引用任務,一個 weak 對象沒有其他對象強引用,會被立即釋放
4. 如果屬性 是 strong,除了會話之外,vc同樣對任務強引用(session 爲什麼要使用強引用)
*/
// 這個是取消下載任務 (達到暫停的目的)
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
NSLog(@"---> %tu", resumeData);
// 記錄續傳數據
self.resumeData = resumeData;
// 將暫停時候的數據保存起來(如果不保存下次啓動的時候是沒有數據的)
// 釋放了下載任務
self.task = nil;
// 將下載任務釋放避免多次暫停的問題
}];
}
- 繼續下載任務
- (IBAction)resume {
// 需要避免下載任務重複創建
if (self.resumeData == nil) {
NSLog(@"沒有續傳數據");
return;
}
// 使用續傳數據,重新實例化下載任務
// 一旦續傳的下載任務被建立之後,續傳數據就沒有用了!
self.task = [self.session downloadTaskWithResumeData:self.resumeData]; self.resumeData = nil;
[self.task resume];
}