一. 前言
很多人都用過(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è)抽象類,如果要使用那么只能使用它的子類弃酌。
- 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。