網(wǎng)絡(luò)請(qǐng)求NSURLSession

一. 前言

很多人都用過(guò)AFNetWorking和SDWebImage屡立,其實(shí)底層就是封裝了NSURLSession來(lái)請(qǐng)求任務(wù)。第三方用多了搀军,對(duì)于蘋果原生的網(wǎng)絡(luò)請(qǐng)求知識(shí)卻掌握得不夠牢固膨俐,所以寫(xiě)這篇文章來(lái)總結(jié)回顧一下。

蘋果在 iOS9 之后已經(jīng)放棄了 NSURLConnection罩句,所以在現(xiàn)在的實(shí)際開(kāi)發(fā)中焚刺,一般使用的是 iOS7 之后推出的 NSURLSession。NSURLSession 和 NSURLConnection 都提供了與各種協(xié)議门烂,諸如 HTTP 和 HTTPS 乳愉,進(jìn)行交互的API。會(huì)話對(duì)象(NSURLSession 類對(duì)象)就是用于管理這種交互過(guò)程屯远。它是一個(gè)高度可配置的容器蔓姚,通過(guò)使用其提供的APPI,可進(jìn)行細(xì)粒度的管理控制慨丐。它提供了在 NSURLConnection 中的所有特性坡脐,此外,它還可以實(shí)現(xiàn) NSURLConnection 不能完成的任務(wù)房揭,例如實(shí)現(xiàn)私密瀏覽备闲。

總之晌端,NSURLSession 有如下優(yōu)勢(shì):

  • NSURLSession 支持 http2.0 協(xié)議
  • 在處理下載任務(wù)的時(shí)候可以直接把數(shù)據(jù)下載到磁盤(通過(guò)配置)
  • 支持后臺(tái)下載|上傳(通過(guò)配置)
  • 同一個(gè) session 發(fā)送多個(gè)請(qǐng)求,只需要建立一次連接(復(fù)用了TCP)
  • 提供了全局的 session 并且可以統(tǒng)一配置浅役,使用更加方便
  • 下載的時(shí)候是多線程異步處理斩松,效率更高

一. NSURLSession的介紹

NSURLSession的配置和創(chuàng)建

NSURLSession對(duì)象是一個(gè)會(huì)話,你可以把他當(dāng)成是生產(chǎn)網(wǎng)絡(luò)請(qǐng)求任務(wù)的工廠觉既。NSURLSession有三種工作模式:

  • 默認(rèn)會(huì)話模式(default):工作模式類似于原來(lái)的NSURLConnection惧盹,使用的是基于磁盤緩存的持久化策略,使用用戶keychain中保存的證書(shū)進(jìn)行認(rèn)證授權(quán)瞪讼。

  • 瞬時(shí)會(huì)話模式(ephemeral):該模式不使用磁盤保存任何數(shù)據(jù)钧椰。所有和會(huì)話相關(guān)的caches,證書(shū)符欠,cookies等都被保存在RAM中嫡霞,因此當(dāng)程序使會(huì)話無(wú)效,這些緩存的數(shù)據(jù)就會(huì)被自動(dòng)清空希柿。(可以實(shí)現(xiàn)私密瀏覽)

  • 后臺(tái)會(huì)話模式(background):該模式在后臺(tái)完成上傳和下載(在系統(tǒng)的一個(gè)單獨(dú)的進(jìn)程中執(zhí)行)诊沪,在創(chuàng)建Configuration對(duì)象的時(shí)候需要提供一個(gè)NSString類型的ID用于標(biāo)識(shí)完成工作的后臺(tái)會(huì)話。

簡(jiǎn)單點(diǎn)理解曾撤,就是不同的工作模式用來(lái)解決不同的網(wǎng)絡(luò)請(qǐng)求場(chǎng)景端姚,當(dāng)你需要緩存會(huì)話相關(guān)的caches,證書(shū)挤悉,cookies等是就使用默認(rèn)會(huì)話模式渐裸,不需要就用瞬時(shí)會(huì)話模式,需要在后臺(tái)進(jìn)行上傳下載就用后臺(tái)會(huì)話模式装悲。

那怎么設(shè)置這些模式呢昏鹃?
這些工作模式是由NSURLSessionConfiguration決定的。以下三種創(chuàng)建方式一一對(duì)應(yīng)NSURLSession的三種工作模式诀诊,其實(shí)本質(zhì)上還是NSURLSessionConfiguration內(nèi)部屬性所決定洞渤,也省得我們一一去設(shè)置。

+ (NSURLSessionConfiguration *)defaultSessionConfiguration;  
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;  
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier; 

一個(gè)NSURLSessionConfiguration對(duì)象定義的行為和策略被用于使用NSURLSession上傳和下載數(shù)據(jù)属瓣,創(chuàng)建一個(gè)配置對(duì)象始終是你必須采取的第一步您宪。你可以使用這個(gè)對(duì)象去配置你想要使用的NSURLSession對(duì)象的超時(shí)時(shí)間、緩存策略奠涌、連接請(qǐng)求以及其它類型的信息宪巨。
以下我們了解下NSURLSessionConfiguration的常見(jiàn)屬性(如果想了解更多屬性,可以看這篇文章NSURLSessionConfiguration詳解):

//如果在后臺(tái)任務(wù)正在傳輸時(shí)程序退出溜畅,可以使用這個(gè)identifier在程序重新啟動(dòng)是創(chuàng)建一個(gè)新的configuration和session關(guān)聯(lián)之前傳輸捏卓。
@property(readonly, copy) NSString  *identifier;

//默認(rèn)為空,NSURLRequest附件的請(qǐng)求頭。
//這個(gè)屬性會(huì)給所有使用該configuration的session生成的tasks中的NSURLRequest添加額外的請(qǐng)求頭怠晴。
//如果這里邊添加的請(qǐng)求頭跟NSURLRequest中重復(fù)了遥金,側(cè)優(yōu)先使用NSURLRequest中的頭
@property(copy) NSDictionary  *HTTPAdditionalHeaders;

//是否使用蜂窩網(wǎng)絡(luò),默認(rèn)是yes.
@property BOOL allowsCellularAccess;

//給request指定每次接收數(shù)據(jù)超時(shí)間隔
//如果下一次接受新數(shù)據(jù)用時(shí)超過(guò)該值蒜田,則發(fā)送一個(gè)請(qǐng)求超時(shí)給該request稿械。默認(rèn)為60s
@property NSTimeInterval  timeoutIntervalForRequest;

//給指定resource設(shè)定一個(gè)超時(shí)時(shí)間,resource需要在時(shí)間到達(dá)之前完成
//默認(rèn)是7天冲粤。
@property NSTimeInterval  timeoutIntervalForResource;

//discretionary屬性為YES時(shí)表示當(dāng)程序在后臺(tái)運(yùn)作時(shí)由系統(tǒng)自己選擇最佳的網(wǎng)絡(luò)連接配置美莫,該屬性可以節(jié)省通過(guò)蜂窩連接的帶寬。
//在使用后臺(tái)傳輸數(shù)據(jù)的時(shí)候梯捕,建議使用discretionary屬性厢呵,而不是allowsCellularAccess屬性,因?yàn)樗鼤?huì)把WiFi和電源可用性考慮在內(nèi)傀顾。
@property (getter=isDiscretionary) BOOL discretionary;

//表示當(dāng)后臺(tái)傳輸結(jié)束時(shí)襟铭,是否啟動(dòng)app.這個(gè)屬性只對(duì) 生效,其他configuration類型會(huì)自動(dòng)忽略該值短曾。默認(rèn)值是YES寒砖。
@property BOOL sessionSendsLaunchEvents;

//是否啟動(dòng)通道,可以用于加快網(wǎng)絡(luò)請(qǐng)求嫉拐,默認(rèn)是NO
@property BOOL HTTPShouldUsePipelining;

/===========儲(chǔ)存的相關(guān)屬性=============/

//存儲(chǔ)cookie哩都,清除存儲(chǔ),直接set為nil即可椭岩。
//對(duì)于默認(rèn)和后臺(tái)的session茅逮,使用sharedHTTPCookieStorage璃赡。
//對(duì)于短暫的session判哥,cookie僅僅儲(chǔ)存到內(nèi)存,session失效時(shí)會(huì)自動(dòng)清除碉考。
@property(retain) NSHTTPCookieStorage  *HTTPCookieStorage;

//默認(rèn)為yes,是否提供來(lái)自shareCookieStorge的cookie
//如果想要自己提供cookie塌计,可以使用HTTPAdditionalHeaders來(lái)提供。
@property BOOL  HTTPShouldSetCookies;

//證書(shū)存儲(chǔ)侯谁,如果不使用锌仅,可set為nil.
//默認(rèn)和后臺(tái)session,默認(rèn)使用的sharedCredentialStorage.
//短暫的session使用一個(gè)私有存儲(chǔ)在內(nèi)存中墙贱。session失效會(huì)自動(dòng)清除热芹。
@property(retain) NSURLCredentialStorage *URLCredentialStorage;

//緩存NSURLRequest的response。
//默認(rèn)的configuration惨撇,默認(rèn)值的是sharedURLCache伊脓。
//后臺(tái)的configuration,默認(rèn)值是nil
//短暫的configuration魁衙,默認(rèn)一個(gè)私有的cache于內(nèi)存报腔,session失效株搔,cache自動(dòng)清除。
@property(retain) NSURLCache  *URLCache;

//緩存策略纯蛾,用于設(shè)置該會(huì)話中的Request的cachePolicy纤房,如果Request有單獨(dú)設(shè)置的話,以Request為準(zhǔn)翻诉。
//默認(rèn)值是NSURLRequestUseProtocolCachePolicy
@property NSURLRequestCachePolicy requestCachePolicy;

那我們?cè)撛趺磩?chuàng)建想要的會(huì)話對(duì)象呢炮姨?總共有三種創(chuàng)建方式,我們可以創(chuàng)建對(duì)應(yīng)的配置類(NSURLSessionConfiguration)來(lái)配置米丘。

//使用靜態(tài)的sharedSession方法剑令,該類使用共享的會(huì)話,該會(huì)話使用全局的Cache拄查,Cookie和證書(shū)
+ (NSURLSession *)sharedSession;  

//通過(guò)sessionWithConfiguration:方法創(chuàng)建對(duì)象吁津,也就是創(chuàng)建對(duì)應(yīng)配置的會(huì)話,與NSURLSessionConfiguration合作使用
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;  

//通過(guò)設(shè)置配置堕扶、代理碍脏、隊(duì)列來(lái)創(chuàng)建會(huì)話對(duì)象
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue; 

二三兩種方式可以創(chuàng)建一個(gè)新會(huì)話并定制其會(huì)話類型。該方式中指定了session的委托和委托所處的隊(duì)列稍算。當(dāng)不再需要連接時(shí)典尾,可以調(diào)用Session的invalidateAndCancel直接關(guān)閉,或者調(diào)用finishTasksAndInvalidate等待當(dāng)前Task結(jié)束后關(guān)閉糊探。這時(shí)Delegate會(huì)收到URLSession:didBecomeInvalidWithError:這個(gè)事件钾埂。Delegate收到這個(gè)事件之后會(huì)被解引用。

NSURLSession支持的任務(wù)

之前提到的科平,NSURLSession對(duì)象生產(chǎn)網(wǎng)絡(luò)請(qǐng)求任務(wù)的工廠褥紫,它能生成出三種類型的任務(wù):加載數(shù)據(jù),下載和上傳瞪慧。而這個(gè)任務(wù)類就是NSURLSessionTask髓考。
NSURLSessionTask 是一個(gè)抽象類,如果要使用那么只能使用它的子類弃酌。

NSURLSessionTask.png
  • NSURLSessionDataTask,可以用來(lái)處理一般的網(wǎng)絡(luò)請(qǐng)求氨菇,如 GET | POST 請(qǐng)求等。
  • NSURLSessionUploadTask妓湘,用于處理上傳請(qǐng)求查蓉。
  • NSURLSessionDownloadTask,主要用于處理下載請(qǐng)求榜贴。

下面我們來(lái)詳細(xì)介紹下三種任務(wù):

① NSURLSessionDataTask

我們可以通過(guò)request對(duì)象或url創(chuàng)建NSURLSessionDataTask對(duì)象豌研。也可以選擇用代理或者Block來(lái)接收數(shù)據(jù)。

//這兩個(gè)方法需要設(shè)置代理來(lái)接收數(shù)據(jù)
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;

//這兩個(gè)方法在completionHandler來(lái)接收數(shù)據(jù)
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

使用Block的回調(diào)接收數(shù)據(jù)的方法比較簡(jiǎn)潔,但是卻沒(méi)有設(shè)置代理聂沙,通過(guò)代理獲取數(shù)據(jù)來(lái)得靈活秆麸。這邊重點(diǎn)介紹代理的方式,順便回顧一下上面所講的NSURLSession的配置和創(chuàng)建及汉。

以下就分別舉兩個(gè)例子來(lái)說(shuō)明(代理和Block):
(1)代理的方式:
代理的方式的話需要設(shè)置代理人沮趣,遵守協(xié)議NSURLSessionDelegate和NSURLSessionTaskDelegate。
說(shuō)到協(xié)議坷随,幾個(gè)類都有對(duì)應(yīng)的協(xié)議房铭,比如NSURLSessionDataTask就對(duì)應(yīng)NSURLSessionDataDelegate,該協(xié)議主要用來(lái)處理dataTask的數(shù)據(jù)處理(比如接收到響應(yīng)温眉,接收到數(shù)據(jù)缸匪,是否緩存數(shù)據(jù))。另外类溢,也經(jīng)常需要遵守公共的協(xié)議:NSURLSessionDelegate和NSURLSessionTaskDelegate凌蔬。
NSURLSessionDelegate主要關(guān)于會(huì)話的,比如會(huì)話關(guān)閉闯冷,會(huì)話收到挑戰(zhàn)砂心。
NSURLSessionTaskDelegate主要關(guān)于任務(wù)的,可以理解為任務(wù)共用的協(xié)議蛇耀,比如任務(wù)完成或失敗辩诞,任務(wù)收到挑戰(zhàn),任務(wù)將要執(zhí)行HTTP重定向纺涤。

還有一個(gè)重點(diǎn)译暂,就是關(guān)于緩存。
之前提到的NSURLSession默認(rèn)的會(huì)話模式撩炊,也就是defaultSessionConfiguration的配置外永,可以將緩存存儲(chǔ)在磁盤上(本質(zhì)就是使用NSURLCache,具體介紹可以看這邊文章NSURLCache緩存使用簡(jiǎn)介)衰抑。
路徑是沙盒路徑下Library/Caches/bundid/Cache.db(對(duì)于webview的緩存象迎,也一樣荧嵌,因?yàn)樗彩怯玫腘SURLCache)呛踊。

另外,還有一個(gè)常見(jiàn)的設(shè)置就是cachePolicy(緩存策略)啦撮,該設(shè)置決定要不要從緩存中獲取谭网,比如request.cachePolicy = NSURLRequestUseProtocolCachePolicy。以下還有幾種策略:

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    //對(duì)特定的 URL 請(qǐng)求使用網(wǎng)絡(luò)協(xié)議中實(shí)現(xiàn)的緩存邏輯赃春。這是默認(rèn)的策略愉择。
    NSURLRequestUseProtocolCachePolicy = 0,

    //數(shù)據(jù)需要從原始地址加載。不使用現(xiàn)有緩存。
    NSURLRequestReloadIgnoringLocalCacheData = 1,

    // 不僅忽略本地緩存锥涕,同時(shí)也忽略代理服務(wù)器或其他中間介質(zhì)目前已有的衷戈、協(xié)議允許的緩存。
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, 

    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,

    //無(wú)論緩存是否過(guò)期层坠,先使用本地緩存數(shù)據(jù)殖妇。如果緩存中沒(méi)有請(qǐng)求所對(duì)應(yīng)的數(shù)據(jù),那么從原始地址加載數(shù)據(jù)破花。
    NSURLRequestReturnCacheDataElseLoad = 2,

    //無(wú)論緩存是否過(guò)期谦趣,先使用本地緩存數(shù)據(jù)。如果緩存中沒(méi)有請(qǐng)求所對(duì)應(yīng)的數(shù)據(jù)座每,那么放棄從原始地址加載數(shù)據(jù)前鹅,請(qǐng)求視為失敗(即:“離線”模式)峭梳。
    NSURLRequestReturnCacheDataDontLoad = 3,

    //從原始地址確認(rèn)緩存數(shù)據(jù)的合法性后舰绘,緩存數(shù)據(jù)就可以使用,否則從原始地址加載葱椭。
    NSURLRequestReloadRevalidatingCacheData = 5, 
};

例子中除盏,我們?cè)O(shè)置了默認(rèn)配置,請(qǐng)求的緩存策略為NSURLRequestReturnCacheDataElseLoad挫以,這樣當(dāng)我們發(fā)送一個(gè)請(qǐng)求后者蠕,就會(huì)將數(shù)據(jù)和響應(yīng)保存在沙盒中的數(shù)據(jù)庫(kù),當(dāng)我們下次發(fā)送請(qǐng)求掐松,會(huì)先從緩存中找踱侣,找不到再去服務(wù)器找。

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDelegate,NSURLSessionTaskDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self sendRequest];
}
- (void)sendRequest{
    //創(chuàng)建請(qǐng)求
    NSURL *url = [NSURL URLWithString:@"http://httpbin.org/get"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //設(shè)置request的緩存策略(決定該request是否要從緩存中獲却蠡恰)
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    //創(chuàng)建配置(決定要不要將數(shù)據(jù)和響應(yīng)緩存在磁盤)
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //configuration.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    //創(chuàng)建會(huì)話
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    //生成任務(wù)
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    //創(chuàng)建的task是停止?fàn)顟B(tài)抡句,需要我們?nèi)?dòng)
    [task resume];
}
//1.接收到服務(wù)器響應(yīng)的時(shí)候調(diào)用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
    NSLog(@"接收響應(yīng)");
    //必須告訴系統(tǒng)是否接收服務(wù)器返回的數(shù)據(jù)
    //默認(rèn)是completionHandler(NSURLSessionResponseAllow)
    //可以再這邊通過(guò)響應(yīng)的statusCode來(lái)判斷否接收服務(wù)器返回的數(shù)據(jù)
    completionHandler(NSURLSessionResponseAllow);
}
//2.接受到服務(wù)器返回?cái)?shù)據(jù)的時(shí)候調(diào)用,可能被調(diào)用多次
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    NSLog(@"接收到數(shù)據(jù)");
    //一般在這邊進(jìn)行數(shù)據(jù)的拼接,在方法3才將完整數(shù)據(jù)回調(diào)
//    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
}
//3.請(qǐng)求完成或者是失敗的時(shí)候調(diào)用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
    NSLog(@"請(qǐng)求完成或者是失敗");
    //在這邊進(jìn)行完整數(shù)據(jù)的解析杠愧,回調(diào)
}
//4.將要緩存響應(yīng)的時(shí)候調(diào)用(必須是默認(rèn)會(huì)話模式待榔,GET請(qǐng)求才可以)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler{
    //可以在這邊更改是否緩存,默認(rèn)的話是completionHandler(proposedResponse)
    //不想緩存的話可以設(shè)置completionHandler(nil)
    completionHandler(proposedResponse);
}
@end

(2)Block的方式:

NSURL *url = [NSURL URLWithString:@"http://www.connect.com/login"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"username=Tom&pwd=123" dataUsingEncoding:NSUTF8StringEncoding];

//使用全局的會(huì)話
NSURLSession *session = [NSURLSession sharedSession];
// 通過(guò)request初始化task
NSURLSessionTask *task = [session dataTaskWithRequest:request
                                   completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
    NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
 }];
//創(chuàng)建的task是停止?fàn)顟B(tài)流济,需要我們?nèi)?dòng)
[task resume];
② NSURLSessionUploadTask

UploadTask繼承自DataTask锐锣。因?yàn)閁ploadTask只不過(guò)在Http請(qǐng)求的時(shí)候,把數(shù)據(jù)放到Http Body中绳瘟。所以雕憔,用UploadTask來(lái)做的事情,通常直接用DataTask也可以實(shí)現(xiàn)糖声。
NSURLSessionUploadTask通過(guò)request創(chuàng)建斤彼,在上傳時(shí)指定文件源或數(shù)據(jù)源分瘦,可以用代理或Block指定任務(wù)完成后的回調(diào)代碼。

/=========代理方式===========/
//通過(guò)文件url來(lái)上傳
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;  
//通過(guò)文件data來(lái)上傳
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;  
//通過(guò)文件流來(lái)上傳
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;

/=========Block方式===========/
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;  

這三種上傳方式分別針對(duì)什么應(yīng)用場(chǎng)景呢琉苇?

  • NSData:如果對(duì)象已經(jīng)在內(nèi)存里
  • File:如果對(duì)象在磁盤上嘲玫,這樣做有助于降低內(nèi)存使用
  • Stream:通過(guò)流對(duì)象,你可以不用一次性將所有的流數(shù)據(jù)加載到內(nèi)存中
    不過(guò)使用Stream一定要實(shí)現(xiàn)URLSession:task:needNewBodyStream:并扇,因?yàn)镾ession沒(méi)辦法在重新嘗試發(fā)送Stream的時(shí)候找到數(shù)據(jù)源趁冈。

這邊我就舉個(gè)上傳圖片的例子:

- (void)uploadRequest{
    //創(chuàng)建請(qǐng)求
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.freeimagehosting.net/upload.php"]];
    //如果是上傳文字就是@"application/json"
    [request addValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
    [request addValue:@"text/html" forHTTPHeaderField:@"Accept"];
    [request setHTTPMethod:@"POST"];
    [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
    [request setTimeoutInterval:20];
    NSData * imagedata = UIImageJPEGRepresentation([UIImage imageNamed:@"person"],1.0);
    
    //創(chuàng)建配置(決定要不要將數(shù)據(jù)和響應(yīng)緩存在磁盤)
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //創(chuàng)建會(huì)話
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    
    NSURLSessionUploadTask * uploadtask = [session uploadTaskWithRequest:request fromData:imagedata completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        //發(fā)送完成的回調(diào)
        
    }];
    [uploadtask resume];
}
//發(fā)送數(shù)據(jù)過(guò)程中會(huì)執(zhí)行(執(zhí)行多次)
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    NSLog(@"發(fā)送數(shù)據(jù)中");
    //在這邊監(jiān)聽(tīng)發(fā)送的進(jìn)度
    //progress = totalBytesSent/(float)totalBytesExpectedToSend
}
③ NSURLSessionDownloadTask

在前面請(qǐng)求數(shù)據(jù)的時(shí)候就相當(dāng)于一個(gè)簡(jiǎn)單的下載過(guò)程,所以說(shuō)NSURLSessionDataTask也是可以做到簡(jiǎn)單下載功能的拜马,比如下載圖片(請(qǐng)求回圖片數(shù)據(jù))渗勘。
但是對(duì)比普通請(qǐng)求,NSURLSessionDownloadTask的功能更為強(qiáng)大:

  • 下載文件可以實(shí)現(xiàn)斷點(diǎn)下載
  • 內(nèi)部已經(jīng)完成了邊接收數(shù)據(jù)邊寫(xiě)入沙盒的操作(直接下載到磁盤)
  • 支持BackgroundSession(后臺(tái)下載)
    接下來(lái)我們來(lái)了解一下下載的API,除了通過(guò)url或request下載外俩莽,還可以通過(guò)之前已經(jīng)下載的數(shù)據(jù)來(lái)創(chuàng)建下載任務(wù)(也就是我們說(shuō)的斷點(diǎn)續(xù)傳)旺坠。同樣地可以通過(guò)completionHandler指定任務(wù)完成后的回調(diào)代碼塊。
/=========代理方式===========/
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;  
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;  
//通過(guò)之前已經(jīng)下載的數(shù)據(jù)來(lái)創(chuàng)建下載任務(wù)
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;  

/=========Block方式===========/
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;  
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;  
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;  

關(guān)于斷點(diǎn)續(xù)傳扮超,使用到的其他方法如下(斷點(diǎn)續(xù)傳的前提是服務(wù)器也支持?jǐn)帱c(diǎn)續(xù)傳):

// 使用這種方式取消下載可以得到將來(lái)用來(lái)恢復(fù)的數(shù)據(jù),保存起來(lái)
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
    self.resumeData = resumeData;
}];

// 由于下載失敗導(dǎo)致的下載中斷會(huì)進(jìn)入此協(xié)議方法,也可以得到用來(lái)恢復(fù)的數(shù)據(jù)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    // 保存恢復(fù)數(shù)據(jù)
    self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
}

// 恢復(fù)下載時(shí)接過(guò)保存的恢復(fù)數(shù)據(jù)
self.task = [self.session downloadTaskWithResumeData:self.resumeData];
// 啟動(dòng)任務(wù)
[self.task resume];

我們舉個(gè)例子來(lái)認(rèn)識(shí)一下普通下載過(guò)程(代理方式取刃,需要遵守NSURLSessionDelegate,NSURLSessionDownloadDelegate):

- (void)downloadRequest{
    //創(chuàng)建請(qǐng)求
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://httpbin.org/image/jpeg"]];
    
    //創(chuàng)建配置(決定要不要將數(shù)據(jù)和響應(yīng)緩存在磁盤)
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //創(chuàng)建會(huì)話
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    
    NSURLSessionDownloadTask * downloadtask = [session downloadTaskWithRequest:request];
    [downloadtask resume];
}

//1. downloadTask下載過(guò)程中會(huì)執(zhí)行
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    NSLog(@"下載中...");
    NSLog(@"寫(xiě)入數(shù)據(jù)大小%lld,總寫(xiě)入數(shù)據(jù)大小%lld出刷,總期望數(shù)據(jù)大小%lld",bytesWritten,totalBytesWritten,totalBytesExpectedToWrite);
    //監(jiān)聽(tīng)下載的進(jìn)度
}
//2.downloadTask下載完成的時(shí)候會(huì)執(zhí)行
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
    NSLog(@"下載完成");
       //該方法內(nèi)部已經(jīng)完成了邊接收數(shù)據(jù)邊寫(xiě)沙盒的操作璧疗,解決了內(nèi)存飆升的問(wèn)題
    //對(duì)數(shù)據(jù)進(jìn)行使用,或者保存(默認(rèn)存儲(chǔ)到臨時(shí)文件夾 tmp 中馁龟,需要剪切文件到 cache)
    
    //保存
    NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];

    //使用
    NSData * data = [NSData dataWithContentsOfURL:location.filePathURL];
    UIImage * image = [UIImage imageWithData:data];
    UIImageWriteToSavedPhotosAlbum(image, nil,nil,nil);
}
//3.請(qǐng)求完成或者是失敗的時(shí)候調(diào)用(Session層次的Task完成的事件)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
    NSLog(@"請(qǐng)求完成或者是失敗");
}

如果想了解一下后臺(tái)下載崩侠,可以看下這篇文章iOS7 Networking with NSURLSession

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坷檩,一起剝皮案震驚了整個(gè)濱河市却音,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矢炼,老刑警劉巖系瓢,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異句灌,居然都是意外死亡夷陋,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門胰锌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)骗绕,“玉大人,你說(shuō)我怎么就攤上這事匕荸〉罚” “怎么了枷邪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵榛搔,是天一觀的道長(zhǎng)诺凡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)践惑,這世上最難降的妖魔是什么腹泌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮尔觉,結(jié)果婚禮上凉袱,老公的妹妹穿的比我還像新娘。我一直安慰自己侦铜,他們只是感情好专甩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著钉稍,像睡著了一般涤躲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贡未,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天种樱,我揣著相機(jī)與錄音,去河邊找鬼俊卤。 笑死嫩挤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的消恍。 我是一名探鬼主播岂昭,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼狠怨!你這毒婦竟也來(lái)了佩抹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤取董,失蹤者是張志新(化名)和其女友劉穎棍苹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體茵汰,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枢里,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蹂午。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栏豺。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豆胸,靈堂內(nèi)的尸體忽然破棺而出奥洼,到底是詐尸還是另有隱情,我是刑警寧澤晚胡,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布灵奖,位于F島的核電站嚼沿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瓷患。R本人自食惡果不足惜骡尽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擅编。 院中可真熱鬧攀细,春花似錦、人聲如沸爱态。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锦担。三九已至故河,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吆豹,已是汗流浹背鱼的。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痘煤,地道東北人凑阶。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像衷快,于是被迫代替她去往敵國(guó)和親宙橱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容