這一篇文章不夠全面,推薦查看個人另外一篇文章:URLSession詳解git
NSURLSession
提供了一個可供經過網絡下載內容的API,而且具備豐富的代理方法。在iOS中,NSURLSession
支持在app未運行或掛起時進行後臺下載。此外,NSURLSession
原生的支持data、file、ftp、http和https URL方案,以及用戶首選項中代理和socks網關。github
使用NSURLSession
,你的app能夠建立一個或多個會話,每個會話協調一組數據傳輸任務。例如:你正在開發一個瀏覽器,你能夠爲每個標籤頁或窗口建立一個會話,或者一個會話用於交互,一個會話用於後臺下載。在每個會話內,能夠添加一系列任務,每個任務表明對特定網址的請求。web
會話由建立配置對象時調用的方法決定。能夠分爲如下幾類:json
NSURLConnection
相同的共享cookie存儲中。經過調用NSURLSessionConfiguration
類中的defaultSessionConfiguration
方法建立默認會話。NSURLSessionConfiguration
類中的ephemeralSessionConfiguration
建立Ephmeral Session。backgroundSessionConfigurationWithIdentifier:
配置的會話由系統在單獨的進程中控制數據傳輸。在iOS中,backgroundSessionConfigurationWithIdentifier:
配置的會話可使應用程序掛起或終止後依然進行傳輸數據。若是app是由系統終止並從新啓動,app可使用相同標誌符建立新配置對象和會話,並恢復到終止時的傳輸狀態;若是用戶從多任務屏幕中終止app,系統會取消全部後臺傳輸任務,此外,系統也不會從新啓動app,必須由用戶啓動app後傳輸任務纔會繼續。經過調用NSURLSessionConfiguration
類中的backgroundSessionConfigurationWithIdentifier:
建立Background Session。// 建立Shared Session
NSURLSession *sharedSession = [NSURLSession sharedSession]; // 只能在completionHandler的塊中獲取數據
// 建立Default Session
NSURLSessionConfiguration *defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultSessionConfiguration delegate:self delegateQueue:nil]; // 在代理方法中獲取數據
// 建立Ephemeral Session
NSURLSessionConfiguration *ephemeralSessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *ephemeralSession = [NSURLSession sessionWithConfiguration:ephemeralSessionConfiguration]; // 在completionHandler的塊中獲取數據
// 建立Background Session
NSURLSessionConfiguration *backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"identifier"];
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration]; // 在completionHandler的塊中獲取數據
複製代碼
會話對象配置還包括對URL緩存和cookie存儲對象引用,這些對象用於發出請求、處理響應時使用,具體狀況取決於配置和請求類型。api
會話對象會對代理保持強引用,直到你退出app或顯式銷燬會話。若是沒有銷燬會話,會形成內存泄漏直至退出app。瀏覽器
在會話中,建立任務用於上傳數據到服務器,而後從服務器取回文件到本地磁盤或NSData對象到內存中,NSURLSession
API提供三種Task類型。緩存
和其餘網絡請求API同樣,NSURLSession
也是異步的,根據你Session配置的不一樣,如下面兩種方式之一返回數據。bash
completionHandler
的塊中獲取數據,當傳輸完成或失敗後調用塊。Background Session
必須包含一個單獨的標誌符,標誌符不能爲nil或空字符串,用於在app崩潰、終止或掛起後將會話再次匹配。resume
方法任務纔會執行。學習新的API最好的方法就是使用代碼練習,下面經過一個小例子來學習NSURLSession
。服務器
啓動Xcode8.2,點擊Create a new Xcode project,在iOS模板列表裏選擇Single View Application模板。cookie
填寫工程名稱,我這裏命名爲NSURLSession1,選擇文件位置,最後點擊Create。
使用NSURLSession
最重要的一點是理解NSURLSession
的實例對象是一切的核心,這個對象處理請求和響應、配置請求、管理會話存儲和狀態。有多種建立會話的方法,最快速的建立方法是使用NSURLSession
的sharedSession
類方法,以下所示:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
}
複製代碼
如上面代碼所示,在ViewController.m
的viewDidLoad
方法內建立一個session
對象。
咱們將使用session
對象查詢iTunes Search API上的軟件。iTunes Search API不須要身份驗證,所以特別適合咱們的例子。想要查詢iTunes Search API咱們須要向 https://itunes.apple.com/search
發送請求、傳遞參數。建立一個NSURLSessionDataTask
的實例dataTask
用來查詢iTunes Search API,查看更新後的viewDidLoad
方法。
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@",json);
}];
}
複製代碼
在這個實例中,咱們調用dataTaskWithURL: completionHandler:
方法,並傳遞一個NSURL
的實例和一個完成處理程序。完成處理程序有三個參數,分別是響應的數據(NSData
)、響應對象(NSURLResponse
)和錯誤(NSError
)。若是請求成功,error
爲nil
。由於咱們知道返回的是JSON格式數據,將取回的數據使用JSONObjectWithData: options: error:
方法解析數據並輸出到控制檯。
使用
NSURLSession
和NSURLConnection
時還有一點須要理解。若是請求失敗,完成處理程序中的error
包含錯誤信息;若是請求返回404
(404表示客戶端能夠與服務器通訊,但服務器找不到請求內容),則請求成功,只是服務器沒有找到請求內容,此時error
爲nil
。
全部新建立的任務都是掛起狀態,必須調用resume
方法纔會執行。更新後的viewDidLoad
以下。
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@",json);
}];
[dataTask resume];
}
複製代碼
運行demo,能夠看到控制檯輸出的數據。
在上一個示例中,經過完成處理處理程序獲取返回的數據,另外還能夠經過NSURLSessionDownloadDelegate
方法獲取數據。下面經過使用NSURLSession
和NSURLSessionDownloadDelegate
獲取圖片,利用UIProgressView
顯示圖片下載進度,下載過程當中能夠點擊Cancel 按鈕暫停下載,點擊Resume 按鈕恢復下載。利用觀察者(Key Value Observing)實現不一樣狀態時Cancel 按鈕和Resume 按鈕的顯示或隱藏狀態。
打開Main.storyboard,從組件庫(Object Library)中拖拽一個UIImageView
到View Controller
,調整大小至全屏,拖拽一個UIProgressView
到屏幕中心,拖拽UIButton
到左上角有橫豎兩條輔助虛線位置,右上角一樣方法放置一個UIButton
。建立完成後以下圖:
拖拽UIImageView
到ViewController.m
的接口部分並命名爲imageView
,類型選擇爲IBOutlet;UIProgressView
在storyboard中不易選中,咱們能夠從大綱試圖中選中並拖拽到ViewController.m
的接口部分,命名爲progressView
,類型選擇IBOutlet;分別拖拽左上角和右上角的UIButton
到接口部分,分別命名爲cancelButton
、resumeButton
,類型選擇爲IBOutlet;分別拖拽左上角、右上角兩個UIButton
到接口部分,名稱分別爲cancle
、resume
,類型選擇爲IBAction。在Attribute Inspector 中修改左上角按鈕名稱爲Cancel,右上角按鈕名稱爲Resume。完成後接口部分代碼以下:
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UIProgressView *progressView;
@property (strong, nonatomic) IBOutlet UIButton *cancelButton;
@property (strong, nonatomic) IBOutlet UIButton *resumeButton;
- (IBAction)cancel:(id)sender;
- (IBAction)resume:(id)sender;
@end
複製代碼
點擊屏幕右上角的工具欄中的Assistant Editor,以後將編輯器選擇攔從Automatic 改成Preview,這樣能夠預覽咱們建立的用戶界面。以下圖:
點擊Preview界面底部的Rotate 按鈕,發現屏幕顯示的控件都在屏幕左側。
下面進行自動佈局設置。選中imageView
、cancelButton
和resumeButton
點擊右下角Resolve AutoLayout Issues,在彈出菜單中選擇Selected Views下面的Reset to Suggested Constraints選項;選中progressView
,點擊右下角的Align按鈕,勾選彈出菜單中的Horizontally in Container和Vertically in Container,點擊Add Constraints。若是此時有警告,按照提示選擇修復或添加約束。至此,自定義佈局完成。
在ViewController.m
接口文件聲明session
、downloadTask
,另外,ViewController
須要遵照NSURLSessionDelegate
和NSURLSessionDownloadDelegate
協議。更新後的接口部分以下:
#import "ViewController.h"
@interface ViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UIProgressView *progressView;
@property (strong, nonatomic) IBOutlet UIButton *cancelButton;
@property (strong, nonatomic) IBOutlet UIButton *resumeButton;
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSURLSessionDownloadTask *downloadTask;
@property (strong, nonatomic) NSData *resumeData; // 用於存儲暫停下載時的數據
- (IBAction)cancel:(id)sender;
- (IBAction)resume:(id)sender;
@end
複製代碼
sharedSession
類方法。首先實現會話的取值方法,使用defaultConfiguration
方法建立會話配置,用會話配置實例化會話對象,設定ViewController
爲會話的代理,代碼以下:- (NSURLSession *)session
{
if (! _session)
{
// 建立會話配置
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 建立會話
_session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
}
return _session;
}
複製代碼
實現progressView
的設值方法。由於使用純代碼建立的UIProgressView
默認值是0,使用storyboard建立的UIProgressView
的默認值是0.5,這裏須要將UIProgressView
初始值設置爲0,否則每次啓動時顯示下載進度在一半,以後進度再從0開始。
- (void)setProgressView:(UIProgressView *)progressView
{
if (_progressView != progressView)
{
_progressView = progressView;
_progressView.progress = 0;
}
}
複製代碼
在viewDidLoad
方法中建立下載任務,下載https://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg
的圖片。最後調用resume
執行下載任務。
- (void)viewDidLoad
{
[super viewDidLoad];
// 建立下載任務
self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"https://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg"]];
// 執行任務
[self.downloadTask resume];
}
複製代碼
爲了獲取返回的數據,咱們須要實現NSURLSessionDownloadDelegate
協議的三個代理方法,URLSession: downloadTask: didFinishDownloadingToURL:
、URLSession: downloadTask: didResumeAtOffset: expectedTotalBytes:
和URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:
。須要注意的是更新UI時須要回到主線程。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = [UIImage imageWithData:data];
});
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
// 輸出NSLog所在行 方法名稱
NSLog(@"%d %s",__LINE__ ,__PRETTY_FUNCTION__);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
float progress = (double) totalBytesWritten / totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = progress;
});
}
複製代碼
cancel
點擊事件方法包含了點擊Cancel按鈕後的邏輯操做。若是downloadTask
不爲nil
,downloadTask
調用cancelByProducingResumeData
方法,該方法的參數是一個完成塊,完成塊包含NSData
類型的參數。不是全部的下載任務均可以恢復,因此先檢查塊的參數resumeData
是否爲nil
,不爲nil
時將返回的數據保存到ViewController.m
頭文件聲明的resumeData
中。
- (IBAction)cancel:(id)sender
{
if (! self.downloadTask) return;
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
if (resumeData)
{
[self setResumeData:resumeData];
[self setDownloadTask:nil];
}
}];
}
複製代碼
想要恢復下載很容易,在resume
點擊事件中,若是resumeData
不爲nil
,使用resumeData
建立一個新的downloadTask
任務,以後執行下載,清空resumeData
數據。
- (IBAction)resume:(id)sender
{
if (! self.resumeData) return;
// 建立任務
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
// 執行任務
[self.downloadTask resume];
// 清除resumeData數據
[self setResumeData:nil];
}
複製代碼
點擊按鈕不該該一直顯示,下面使用觀察者來決定什麼時候顯示、隱藏點擊按鈕。在viewDidLoad
方法中,隱藏點擊按鈕,將ViewController
添加爲reuseData
和downloadTask
關鍵路徑的觀察者。
- (void)viewDidLoad
{
[super viewDidLoad];
// 添加觀察者
[self addObserver:self forKeyPath:@"resumeData" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@"downloadTask" options:NSKeyValueObservingOptionNew context:NULL];
// 隱藏點擊按鈕
self.cancelButton.hidden = YES;
self.resumeButton.hidden = YES;
// 建立下載任務
self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"https://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg"]];
// 執行任務
[self.downloadTask resume];
}
複製代碼
在obserValueForKeyPath: ofObject: change: context:
方法中,當resumeData
爲nil
時,隱藏Resume按鈕;當downloadTask
爲nil
時,隱藏Cancel按鈕。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"resumeData"])
{
dispatch_async(dispatch_get_main_queue(), ^{
self.resumeButton.hidden = (self.resumeData == nil);
});
}
else if ([keyPath isEqualToString:@"downloadTask"])
{
dispatch_async(dispatch_get_main_queue(), ^{
self.cancelButton.hidden = (self.downloadTask == nil);
});
}
else
// 若是遇到沒有觀察的屬性,將其交由父類處理,父類可能也在觀察該屬性。
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
複製代碼
注意:由於不知道是否在主線程調用
obserValueForKeyPath: ofObject: change: context:
方法,這裏更新UI時須要回到主線程。
最後,移除觀察者。
- (void)dealloc {
// 移除觀察者。
[self removeObserver:self forKeyPath:@"resumeData"];
[self removeObserver:self forKeyPath:@"downloadTask"];
}
複製代碼
想要深刻學習觀察者,查看KVC和KVO學習筆記這篇文章。
會話對代理是強引用,也就是隻要會話是活躍的代理就不會被釋放,這樣會形成內存泄露,所以任務執行完畢後須要將會話銷燬。有多種方法銷燬會話,由於這裏咱們只下載了一張圖片,咱們能夠在圖片下載完成後銷燬會話。下面是更新後的URLSession: downloadTask: didFinishDownloadToURL:
方法,圖片下載完成後把Cancle按鈕和progressView
設置爲隱藏。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.hidden = YES; // 下載完成後隱藏進度條
self.cancelButton.hidden = YES; // 下載完成後隱藏Cancel按鈕
self.imageView.image = [UIImage imageWithData:data];
});
// 銷燬會話
[session finishTasksAndInvalidate];
}
複製代碼
經過這篇文章的講解,你應該對NSURLSession
有一個基本的理解,下一篇文章將經過建立一個簡單的播客來使用後臺下載。
文件名稱:NSURLSession1
源碼下載:github.com/pro648/Basi…
參考資料:
歡迎更多指正:github.com/pro648/tips…