NSURLSession的使用

這一篇文章不夠全面,推薦查看個人另外一篇文章:URLSession詳解git

NSURLSession提供了一個可供經過網絡下載內容的API,而且具備豐富的代理方法。在iOS中,NSURLSession支持在app未運行或掛起時進行後臺下載。此外,NSURLSession原生的支持data、file、ftp、http和https URL方案,以及用戶首選項中代理和socks網關。github

使用NSURLSession,你的app能夠建立一個或多個會話,每個會話協調一組數據傳輸任務。例如:你正在開發一個瀏覽器,你能夠爲每個標籤頁或窗口建立一個會話,或者一個會話用於交互,一個會話用於後臺下載。在每個會話內,能夠添加一系列任務,每個任務表明對特定網址的請求。web

1. Session類型

會話由建立配置對象時調用的方法決定。能夠分爲如下幾類:json

  • Shared Session:獲取單例對象。使用全局共享的會話,而且不能調整cache、cookie和證書等;也不能使用代理方法,所以將不能檢測下載進度一類事件。
  • Default Session:默認會話配置使用基於磁盤持久緩存,將憑據保存在用戶的鑰匙串中,默認將cookie存儲在與NSURLConnection相同的共享cookie存儲中。經過調用NSURLSessionConfiguration類中的defaultSessionConfiguration方法建立默認會話。
  • Ephemeral Session:會把cacehe、cookie和用戶憑據保存在RAM中而不是用戶磁盤中,只有在你主動保存某個URL的內容到某個文件時纔會保存數據,所以最大好處是能夠保護用戶隱私,適用於無痕瀏覽。能夠緩存的數據大小取決於剩餘RAM大小,當會話銷燬後,全部臨時數據均會清除。另外,在iOS中,應用程序掛起時保存在內存中的數據不會被清除,但在應用程序終止或系統內存不足時會被清除。經過調用NSURLSessionConfiguration類中的ephemeralSessionConfiguration建立Ephmeral Session。
  • Background Session:容許在後臺執行HTTP和HTTPS上傳或下載任務。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。瀏覽器

2. Task類型

在會話中,建立任務用於上傳數據到服務器,而後從服務器取回文件到本地磁盤或NSData對象到內存中,NSURLSessionAPI提供三種Task類型。緩存

  • Data Task:發送和接收NSData類型數據,通常用於交互式請求。
  • Upload Task:繼承自Data Task,通常以文件格式傳輸數據。支持app未運行時上傳數據。
  • Download Task:取回的數據是文件格式,支持app未運行時上傳、下載。

和其餘網絡請求API同樣,NSURLSession也是異步的,根據你Session配置的不一樣,如下面兩種方式之一返回數據。bash

  • completionHandler的塊中獲取數據,當傳輸完成或失敗後調用塊。
  • 在代理方法中獲取數據。有多種代理方法可供選擇使用,比上面的方法功能強大些,例如能夠檢測下載進度。

3. NSURLSession的使用步驟

  1. 建立會話配置。對於Background Session必須包含一個單獨的標誌符,標誌符不能爲nil或空字符串,用於在app崩潰、終止或掛起後將會話再次匹配。
  2. 選擇會話配置類型,建立會話。
  3. 建立任務。根據想要實現的功能選擇Task類型。
  4. 執行任務。全部建立的任務默認都是掛起狀態,必須調用resume方法任務纔會執行。

4. 簡單應用

學習新的API最好的方法就是使用代碼練習,下面經過一個小例子來學習NSURLSession服務器

4.1 新建工程

啓動Xcode8.2,點擊Create a new Xcode project,在iOS模板列表裏選擇Single View Application模板。cookie

填寫工程名稱,我這裏命名爲NSURLSession1,選擇文件位置,最後點擊Create

4.2 建立會話

使用NSURLSession最重要的一點是理解NSURLSession的實例對象是一切的核心,這個對象處理請求和響應、配置請求、管理會話存儲和狀態。有多種建立會話的方法,最快速的建立方法是使用NSURLSessionsharedSession類方法,以下所示:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSURLSession *session = [NSURLSession sharedSession];
}
複製代碼

如上面代碼所示,在ViewController.mviewDidLoad方法內建立一個session對象。

4.3 建立任務

咱們將使用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)。若是請求成功,errornil。由於咱們知道返回的是JSON格式數據,將取回的數據使用JSONObjectWithData: options: error:方法解析數據並輸出到控制檯。

使用NSURLSessionNSURLConnection時還有一點須要理解。若是請求失敗,完成處理程序中的error包含錯誤信息;若是請求返回404(404表示客戶端能夠與服務器通訊,但服務器找不到請求內容),則請求成功,只是服務器沒有找到請求內容,此時 errornil

4.4 執行任務

全部新建立的任務都是掛起狀態,必須調用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,能夠看到控制檯輸出的數據。

5. 進階應用

在上一個示例中,經過完成處理處理程序獲取返回的數據,另外還能夠經過NSURLSessionDownloadDelegate方法獲取數據。下面經過使用NSURLSessionNSURLSessionDownloadDelegate獲取圖片,利用UIProgressView顯示圖片下載進度,下載過程當中能夠點擊Cancel 按鈕暫停下載,點擊Resume 按鈕恢復下載。利用觀察者(Key Value Observing)實現不一樣狀態時Cancel 按鈕和Resume 按鈕的顯示或隱藏狀態。

5.1 建立用戶界面

打開Main.storyboard,從組件庫(Object Library)中拖拽一個UIImageViewView Controller,調整大小至全屏,拖拽一個UIProgressView到屏幕中心,拖拽UIButton到左上角有橫豎兩條輔助虛線位置,右上角一樣方法放置一個UIButton。建立完成後以下圖:

拖拽UIImageViewViewController.m的接口部分並命名爲imageView,類型選擇爲IBOutlet;UIProgressView在storyboard中不易選中,咱們能夠從大綱試圖中選中並拖拽到ViewController.m的接口部分,命名爲progressView,類型選擇IBOutlet;分別拖拽左上角和右上角的UIButton到接口部分,分別命名爲cancelButtonresumeButton,類型選擇爲IBOutlet;分別拖拽左上角、右上角兩個UIButton到接口部分,名稱分別爲cancleresume,類型選擇爲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
複製代碼

5.2 自動佈局

點擊屏幕右上角的工具欄中的Assistant Editor,以後將編輯器選擇攔從Automatic 改成Preview,這樣能夠預覽咱們建立的用戶界面。以下圖:

點擊Preview界面底部的Rotate 按鈕,發現屏幕顯示的控件都在屏幕左側。

下面進行自動佈局設置。選中imageViewcancelButtonresumeButton點擊右下角Resolve AutoLayout Issues,在彈出菜單中選擇Selected Views下面的Reset to Suggested Constraints選項;選中progressView,點擊右下角的Align按鈕,勾選彈出菜單中的Horizontally in ContainerVertically in Container,點擊Add Constraints。若是此時有警告,按照提示選擇修復或添加約束。至此,自定義佈局完成。

5.3 建立下載任務

ViewController.m接口文件聲明sessiondownloadTask,另外,ViewController須要遵照NSURLSessionDelegateNSURLSessionDownloadDelegate協議。更新後的接口部分以下:

#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
複製代碼
  1. 由於須要配置會話、使用代理獲取下載進度,這裏就不能使用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];
}
複製代碼

5.4 實現代理方法

爲了獲取返回的數據,咱們須要實現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;
    });
}
複製代碼

5.5 暫停下載

cancel點擊事件方法包含了點擊Cancel按鈕後的邏輯操做。若是downloadTask不爲nildownloadTask調用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];
        }
    }];
}
複製代碼

5.6 恢復下載

想要恢復下載很容易,在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];
}
複製代碼

5.7 添加觀察者

點擊按鈕不該該一直顯示,下面使用觀察者來決定什麼時候顯示、隱藏點擊按鈕。在viewDidLoad方法中,隱藏點擊按鈕,將ViewController添加爲reuseDatadownloadTask關鍵路徑的觀察者。

- (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:方法中,當resumeDatanil時,隱藏Resume按鈕;當downloadTasknil時,隱藏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學習筆記這篇文章。

5.8 銷燬會話

會話對代理是強引用,也就是隻要會話是活躍的代理就不會被釋放,這樣會形成內存泄露,所以任務執行完畢後須要將會話銷燬。有多種方法銷燬會話,由於這裏咱們只下載了一張圖片,咱們能夠在圖片下載完成後銷燬會話。下面是更新後的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…

參考資料:

  1. Networking with NSURLSession: Part 1
  2. Networking with NSURLSession: Part 2
  3. URLSession

歡迎更多指正:github.com/pro648/tips…

本文地址:github.com/pro648/tips…

相關文章
相關標籤/搜索