一、 NSURLSession 基礎(chǔ)知識(shí)
NSURLSession
是協(xié)調(diào)
一組相關(guān)的網(wǎng)絡(luò)數(shù)據(jù)傳輸任務(wù)的對(duì)象,本身是不會(huì)進(jìn)行請(qǐng)求的,而是通過(guò)創(chuàng)建 task (NSURLSessionTask)
的形式進(jìn)行網(wǎng)絡(luò)請(qǐng)求(resume()方法的調(diào)用)。
1.同一個(gè)
NSURLSession
可以創(chuàng)建多個(gè) task,并且這些 task 之間的 cache 和 cookie 是共享的。
2.如果您的應(yīng)用程序創(chuàng)建一個(gè)或多個(gè)NSURLSession
實(shí)例,每個(gè)實(shí)例協(xié)調(diào)一組相關(guān)的數(shù)據(jù)傳輸任務(wù)。
通過(guò)NSURLSession 發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求 一般有如下幾步:
- 1、創(chuàng)建NSURLSession對(duì)象
創(chuàng)建 NSURLSession 對(duì)象之前 根據(jù)需求 可能 會(huì) 創(chuàng)建一個(gè)NSURLSessionConfiguration配置請(qǐng)求。
- 2、通過(guò)session對(duì)象發(fā)起網(wǎng)絡(luò)請(qǐng)求,并獲取task對(duì)象
- 3、 調(diào)用[task resume]方法發(fā)起網(wǎng)絡(luò)請(qǐng)求
NSURLSession
的使用相對(duì)于之前的NSURLConnection
更簡(jiǎn)單,而且不用處理Runloop
相關(guān)的東西。
NSURLSession相關(guān)類為 :
- NSURLSession
- NSURLSessionConfiguration
- NSURLSessionDelegate
- NSURLSessionTask
- NSURLSessionTaskMetrics
- NSURLSessionTaskTransactionMetrics
二、NSURLSession 創(chuàng)建方式
NSURLSession
:請(qǐng)求會(huì)話對(duì)象,可以用系統(tǒng)提供的單例對(duì)象,也可以自己創(chuàng)建。
-
NSURLSession
有三種方式創(chuàng)建:1、
sharedSession
:共享的單例會(huì)話對(duì)象
(沒(méi)有配置對(duì)象),用于基本請(qǐng)求,可以和其他使用這個(gè)session的task共享連接和請(qǐng)求信息,共享會(huì)話使用當(dāng)前設(shè)置的全局NSURLCache
,NSHTTPCookieStorage
和NSURLCredentiaStorage
對(duì)象
有關(guān)更多信息,請(qǐng)參見:shareSession-
2、
sessionWithConfiguration
:使用指定的會(huì)話配置創(chuàng)建會(huì)話。- 2.1、configuration:一個(gè)配置對(duì)象,用于指定某些行為,例如緩存策略,超時(shí),代理,管道,支持的TLS版本,cookie策略,憑據(jù)存儲(chǔ)等。有關(guān)更多信息,請(qǐng)參見 NSURLSessionConfiguration
-
3、
sessionWithConfiguration:delegate:delegateQueue
: 使用指定的會(huì)話配置、委托和操作隊(duì)列創(chuàng)建會(huì)話。 如果想更好的控制請(qǐng)求過(guò)程以及回調(diào)線程,需要上面的方法進(jìn)行初始化操作,并傳入delegate
來(lái)設(shè)置回調(diào)對(duì)象和回調(diào)的線程。- 3.1、 delegate:會(huì)話委托對(duì)象,用于
處理對(duì)身份驗(yàn)證問(wèn)題、做出緩存決策以及處理其他與會(huì)話相關(guān)的的事件的請(qǐng)求
。如果為nil,則該類只能與接受完成處理程序的方法一起使用。
從繼承關(guān)系上,我們就可以理解在初始化的時(shí)候,只通過(guò)設(shè)置NSURLSession對(duì)象的delegate就可以了。因?yàn)楦鶕?jù)不同的task,其實(shí)就是設(shè)置了不同的delegate。這個(gè)設(shè)計(jì)避免了多次設(shè)置delegate的情況,同時(shí)也根據(jù)不同的task實(shí)現(xiàn)不同的delegate方法。
NSURLSessionDelegate: 作為所有代理的基類,定義了網(wǎng)絡(luò)請(qǐng)求最基礎(chǔ)的代理方法。NSURLSessionTaskDelegate – 定義了網(wǎng)絡(luò)請(qǐng)求任務(wù)相關(guān)的代理方法。NSURLSessionDownloadDelegate – 用于下載任務(wù)相關(guān)的代理方法,比如下載進(jìn)度等等。NSURLSessionDataDelegate – 用于普通數(shù)據(jù)任務(wù)和上傳任務(wù)注意:session對(duì)象保持對(duì)委托的強(qiáng)引用,直到你的應(yīng)用程序退出或顯式地使 會(huì)話失效。 如果您沒(méi)有通過(guò)調(diào)用invalidateAndCancel 或 finishTasksAndInvalidate 方法來(lái)使會(huì)話 無(wú)效,則您的應(yīng)用程序會(huì) 泄漏內(nèi)存,直到退出。
- 3.2、 delegateQueue:用于
調(diào)度 委托調(diào)用
和完成處理程序的操作隊(duì)列
。 該隊(duì)列應(yīng)該是一個(gè)串行隊(duì)列
,以確保回調(diào)的正確順序。 如果為nil,則會(huì)話將創(chuàng)建一個(gè)串行操作隊(duì)列,用于執(zhí)行所有委托方法調(diào)用和完成處理程序調(diào)用。
- 3.1、 delegate:會(huì)話委托對(duì)象,用于
@property (class, readonly, strong) NSURLSession *sharedSession;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
有關(guān)更多信息,請(qǐng)參見 sessionWithConfiguration:delegate:delegateQueue:
三、NSURLSessionConfiguration 會(huì)話配置方式
NSURLSessionConfiguration 負(fù)責(zé)對(duì) NSURLSession 初始化時(shí)進(jìn)行配置。
通過(guò) NSURLSessionConfiguration 可以設(shè)置請(qǐng)求的Cookie、密鑰、緩存、請(qǐng)求頭等參數(shù),將網(wǎng)絡(luò)請(qǐng)求的一些配置參數(shù)從 NSURLSession 中分離出來(lái)。
NSURLSessionConfiguration 對(duì)象定義了使用 NSURLSession 對(duì)象 上傳和 下載 數(shù)據(jù)時(shí)使用的行為和策略。當(dāng)上傳或下載數(shù)據(jù)時(shí),創(chuàng)建配置對(duì)象總是您必須采取的第一步。您可以使用此對(duì)象來(lái)配置超時(shí)值、緩存策略、連接要求和其他類型的信息,這些信息您打算與 NSURLSession 對(duì)象一起使用。
在使用 NSURLSessionConfiguration 對(duì)象初始化一個(gè)會(huì)話對(duì)象之前,適當(dāng)?shù)嘏渲盟呛苤匾摹?huì)話對(duì)象復(fù)制您提供的配置設(shè)置,并使用這些設(shè)置來(lái)配置會(huì)話。配置完成后,會(huì)話對(duì)象(session)將忽略您對(duì)NSURLSessionConfiguration對(duì)象所做的任何更改。如果您需要修改您的傳輸策略,您必須更新會(huì)話配置對(duì)象并使用它來(lái)創(chuàng)建一個(gè)新的 NSURLSession 對(duì)象。
請(qǐng)注意:在某些情況下,這個(gè)配置中定義的策略可能會(huì)被一個(gè)為任務(wù)提供的 NSURLRequest 對(duì)象指定的策略所覆蓋。 除非會(huì)話的策略更具限制性,否則將遵守在請(qǐng)求對(duì)象(NSURLRequest)上指定的任何策略。 例如,如果
會(huì)話配置
指定不允許使用蜂窩網(wǎng)絡(luò)(會(huì)話配置限制使用蜂窩網(wǎng)絡(luò)),則NSURLRequest對(duì)象將無(wú)法請(qǐng)求蜂窩網(wǎng)絡(luò)。
3.1、NSURLSessionConfiguration 會(huì)話配置的類型
類方法,創(chuàng)建對(duì)象
- 3.1.1、defaultSessionConfiguration:
默認(rèn)會(huì)話配置
,使用基于磁盤的持久緩存
(將結(jié)果下載到文件時(shí)除外),并將賬戶信息存儲(chǔ)在用戶的鑰匙串中。 它還將cookie(默認(rèn)情況下)存儲(chǔ)在與NSURLSession和NSURLDownload類相同的共享cookie存儲(chǔ)中。
注意:修改 返回的會(huì)話配置對(duì)象
不會(huì)影響
以后調(diào)用defaultSessionConfiguration方法 返回的任何配置對(duì)象 ,并且不會(huì)更改現(xiàn)有會(huì)話的默認(rèn)行為。因此,將 返回的 對(duì)象用作其他自定義的起點(diǎn) 始終是安全的。
@property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
@property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
有關(guān)更多信息,請(qǐng)參見:defaultSessionConfiguration
- 3.1.2、ephemeralSessionConfiguration :
臨時(shí)會(huì)話配置
,跟defaultSessionConfiguration不同的是 會(huì)話對(duì)象不將緩存、憑據(jù)存儲(chǔ)或任何與會(huì)話相關(guān)的數(shù)據(jù)存儲(chǔ)到磁盤。相反,與會(huì)話相關(guān)的數(shù)據(jù)存儲(chǔ)在RAM中(只會(huì)存在內(nèi)存里),所以當(dāng)您的應(yīng)用使會(huì)話無(wú)效時(shí),將自動(dòng)清除所有臨時(shí)會(huì)話數(shù)據(jù)。 此外,在iOS中,暫停應(yīng)用程序時(shí)不會(huì)自動(dòng)清除內(nèi)存中的緩存,但是如果用戶退出并重新啟動(dòng)您的應(yīng)用,則保證不會(huì)存在。
使用臨時(shí)會(huì)話的主要優(yōu)點(diǎn)是隱私。 通過(guò)不將可能敏感的數(shù)據(jù)寫入磁盤,可以減少數(shù)據(jù)被以后攔截和使用的可能性。 因此,臨時(shí)會(huì)話非常適合Web瀏覽器和其他類似情況下的私有瀏覽模式。由于臨時(shí)會(huì)話不會(huì)將緩存的數(shù)據(jù)寫入磁盤,因此緩存的大小受可用RAM的限制,可能會(huì)降低感知的性能,具體取決于您的應(yīng)用程序。
有關(guān)更多信息,請(qǐng)參見:ephemeralSessionConfiguration
- 3.1.3、 backgroundSessionConfigurationWithIdentifier: :使系統(tǒng)在單獨(dú)的進(jìn)程中執(zhí)行上載和下載任務(wù)的一個(gè)配置對(duì)象(
后臺(tái)會(huì)話配置對(duì)象
),該會(huì)話配置對(duì)象允許程序在后臺(tái)執(zhí)行HTTP和HTTPS上傳或下載任務(wù)。另外,系統(tǒng)會(huì)根據(jù)設(shè)備的負(fù)載程度決定分配下載的資源,因此有可能會(huì)很慢甚至超時(shí)失敗- identifier:配置對(duì)象的唯一標(biāo)識(shí)符。 此參數(shù)不能為nil或?yàn)榭兆址?/li>
注意:1. 如果iOS應(yīng)用
被系統(tǒng)終止并重新啟動(dòng)
,則該應(yīng)用可以使用相同的標(biāo)識(shí)符)來(lái)創(chuàng)建新的配置對(duì)象和會(huì)話,并檢索終止時(shí)正在進(jìn)行的傳輸狀態(tài),可以使用同一個(gè) identifier創(chuàng)建configuration和session,并且能恢復(fù)終止時(shí)的傳輸狀態(tài)。此行為僅適用于系統(tǒng)正常終止應(yīng)用程序的情況 。
2.如果用戶從多任務(wù)屏幕中終止
該應(yīng)用程序,則系統(tǒng)會(huì)取消所有會(huì)話的后臺(tái)傳輸。此外,系統(tǒng)不會(huì)自動(dòng)重新啟動(dòng)由用戶強(qiáng)制退出的應(yīng)用程序。用戶必須明確重新啟動(dòng)該應(yīng)用程序,然后才能再次開始傳輸。
有關(guān)更多信息,請(qǐng)參見:backgroundSessionConfigurationWithIdentifier:
有關(guān)使用后臺(tái)配置的示例,請(qǐng)參閱Downloading Files in the Background.
四、NSURLSessionTask 對(duì)象的創(chuàng)建
NSURLSessionTask 類是 URL 會(huì)話中任務(wù)的基類,通過(guò) request 對(duì)象或 URL 創(chuàng)建,但一般不會(huì)直接是 NSURLSessionTask 類,而是基于不同任務(wù)類型,通過(guò)調(diào)用 NSURLSession 實(shí)例中的一個(gè)任務(wù)創(chuàng)建方法來(lái)創(chuàng)建任務(wù)。您調(diào)用的方法決定了任務(wù)的類型。
NSURLSessionTask 類型:
-
1、NSURLSessionDataTask:處理普通的
Get
、Post
數(shù)據(jù)請(qǐng)求。使用 NSURLSession 的相關(guān)方法來(lái)創(chuàng)建NSURLSessionDataTask實(shí)例。 默認(rèn)(default
),臨時(shí)(ephemeral
)和共享(shared
)會(huì)話(sessions
)均支持它們,但后臺(tái)會(huì)話(background sessions
)不支持它們。- 1.1、 NSURLSessionUploadTask:
處理上傳請(qǐng)求
,通過(guò)request創(chuàng)建,在上傳時(shí)指定文件源或數(shù)據(jù)源(以數(shù)據(jù)流的方式進(jìn)行上傳,這種方式好處就是大小不受限制,上傳需要服務(wù)器端腳本支持)。此外,在后臺(tái)會(huì)話中支持上傳任務(wù)。是NSURLSessionDataTask的子類。
有關(guān)更多信息,請(qǐng)參見:NSURLSessionUploadTask
- 1.1、 NSURLSessionUploadTask:
2、NSURLSessionDownloadTask:
處理下載任務(wù)
。下載任務(wù)直接將服務(wù)器的響應(yīng)數(shù)據(jù)寫入一個(gè)臨時(shí)文件
,以便在數(shù)據(jù)從服務(wù)器到達(dá)時(shí)為您的應(yīng)用提供進(jìn)度更新
。 當(dāng)您在后臺(tái)會(huì)話中使用下載任務(wù)
時(shí),即使您的應(yīng)用處于掛起狀態(tài)
或未運(yùn)行
,這些下載也會(huì)繼續(xù)。您可以暫停(取消)
下載任務(wù),并在以后恢復(fù)它們(如果服務(wù)器支持?jǐn)帱c(diǎn)下載的話
)。 您也可以恢復(fù)由于網(wǎng)絡(luò)連接問(wèn)題而失敗的下載。
有關(guān)更多信息,請(qǐng)參見:NSURLSessionDownloadTask3、NSURLSessionStreamTask:
基于流的URL會(huì)話任務(wù)
。使用 NSURLSession 的相關(guān)方法來(lái)創(chuàng)建NSURLSessionStreamTask實(shí)例。NSURLSessionStreamTask對(duì)象執(zhí)行同步讀取和寫入,這些讀取和寫入將依次排隊(duì)和執(zhí)行,并在會(huì)話委托隊(duì)列上完成時(shí)調(diào)用處理程序。 如果取消該任務(wù),則所有排隊(duì)的讀寫操作將以適當(dāng)?shù)腻e(cuò)誤調(diào)用其完成處理程序。
有關(guān)更多信息,請(qǐng)參見:NSURLSessionStreamTask
常見方法:
#pragma mark ------------ 普通數(shù)據(jù)請(qǐng)求方法 ------------
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
#pragma mark ------------ 上傳方法 ------------
//上傳NSData類型的數(shù)據(jù)
//以數(shù)據(jù)流的方式進(jìn)行上傳,這種方式好處就是大小不受限制
// bodyData 請(qǐng)求的正文數(shù)據(jù)
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
//創(chuàng)建一個(gè)任務(wù),該任務(wù)執(zhí)行HTTP請(qǐng)求以上傳指定的文件
//fileURL 要上傳本地的文件的URL
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
//提供URL、緩存策略、請(qǐng)求類型等的URL請(qǐng)求對(duì)象。
//request 通過(guò)流數(shù)據(jù)初始化的請(qǐng)求對(duì)象,上傳流數(shù)據(jù)
//這個(gè)請(qǐng)求對(duì)象request中的body流和body數(shù)據(jù)被忽略,而由fromData提供,會(huì)話調(diào)用其委托的URLSession:task:needNewBodyStream:方法來(lái)提供body數(shù)據(jù)。
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
#pragma mark ------------ 下載方法 ------------
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
//通過(guò)之前已經(jīng)下載的數(shù)據(jù)來(lái)創(chuàng)建下載任務(wù),以恢復(fù)先前取消或失敗的下載,支持?jǐn)帱c(diǎn)下載
//resumeData 之前已經(jīng)下載的數(shù)據(jù)。
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
#pragma mark ------------ 基于流的任務(wù)方法 ------------
//創(chuàng)建 一個(gè) 給定主機(jī) 和 端口 的雙向流任務(wù)
- (NSURLSessionStreamTask *)streamTaskWithHostName:(NSString *)hostname port:(NSInteger)port;
//使用NSNetService創(chuàng)建一個(gè)雙向流任務(wù)來(lái)標(biāo)識(shí)端點(diǎn)。NSNetService將在任何IO完成之前被解析。
- (NSURLSessionStreamTask *)streamTaskWithNetService:(NSNetService *)service;
/*
minBytes:讀取的最小字節(jié)數(shù)
maxBytes:讀取的最大字節(jié)數(shù)
timeout:讀取字節(jié)超時(shí)。如果讀取未在指定的時(shí)間間隔內(nèi)完成,則讀取將被取消,并且會(huì)調(diào)用errorCompleteHandler。 傳遞0以防止讀取超時(shí)。
completionHandler :讀取所有字節(jié)或發(fā)生錯(cuò)誤時(shí)調(diào)用的完成處理程序。 該處理程序在委托隊(duì)列上執(zhí)行。
該完成處理程序采用以下參數(shù):
data: 從流中讀取的數(shù)據(jù)
atEOF:流是否到達(dá)文件結(jié)尾(EOF),從而無(wú)法讀取更多數(shù)據(jù)
error:一個(gè)錯(cuò)誤對(duì)象,指示讀取失敗的原因,如果讀取成功,則為nil
*/
- (void)readDataOfMinLength:(NSUInteger)minBytes
maxLength:(NSUInteger)maxBytes
timeout:(NSTimeInterval)timeout
completionHandler:(void (^)(NSData *data, BOOL atEOF, NSError *error))completionHandler;
/*
異步將指定的數(shù)據(jù)寫入流,并在完成時(shí)調(diào)用處理程序
data:要寫入的數(shù)據(jù)
timeout :寫入字節(jié)超時(shí)。 如果寫操作未在指定的間隔內(nèi)完成,則取消寫操作,并且會(huì)調(diào)用errorCompleteHandler。 傳遞0以防止寫超時(shí)。
*/
- (void)writeData:(NSData *)data
timeout:(NSTimeInterval)timeout
completionHandler:(void (^)(NSError *error))completionHandler;
//獲取流
//完成所有已排隊(duì)的讀取和寫入,然后調(diào)用URLSession:streamTask:didBecomeInputStream:outputStream:delegate消息。
// 收到該消息時(shí),任務(wù)對(duì)象被視為已完成,并且不會(huì)再收到任何委托消息。
- (void)captureStreams;
//完成所有排隊(duì)的讀寫操作,然后關(guān)閉底層套接字的讀端。
//調(diào)用此方法后,可以繼續(xù)使用writeData:timeout:completionHandler:方法寫入數(shù)據(jù)。
//調(diào)用此方法后,對(duì)readDataOfMinLength:maxLength:timeout:completionHandler:的任何調(diào)用都會(huì)導(dǎo)致錯(cuò)誤。
- (void)closeRead;
//在調(diào)用closeWrite方法之后,你可以使用readDataOfMinLength:maxLength:timeout:completionHandler:方法繼續(xù)讀取數(shù)據(jù)。
//在調(diào)用這個(gè)方法之后,任何對(duì)writeData:timeout:completionHandler:的調(diào)用都會(huì)導(dǎo)致一個(gè)錯(cuò)誤。
//因?yàn)榉?wù)器可能會(huì)繼續(xù)向客戶端寫入字節(jié),所以建議您繼續(xù)讀取,直到流到達(dá)文件結(jié)束符(EOF)。
//完成所有排隊(duì)的讀寫操作,然后關(guān)閉底層套接字的寫端。
- (void)closeWrite;
//完成所有進(jìn)入隊(duì)列的讀寫操作,并建立安全連接。
//使用URLSession:task:didReceiveChallenge:completionHandler:方法,將身份驗(yàn)證回調(diào)發(fā)送到會(huì)話的委托。
- (void)startSecureConnection;
#pragma mark ------------ 控制任務(wù)狀態(tài) ------------
//取消當(dāng)前請(qǐng)求。任務(wù)會(huì)被標(biāo)記為取消,并在未來(lái)某個(gè)時(shí)間調(diào)用URLSession:task:didCompleteWithError:方法。
-(void)cancel;
//開始或繼續(xù)請(qǐng)求,創(chuàng)建后的task默認(rèn)是掛起的,需要手動(dòng)調(diào)用resume才可以開始請(qǐng)求。
//如果任務(wù)被掛起,則恢復(fù)任務(wù)。
-(void)resume;
//掛起當(dāng)前請(qǐng)求(暫時(shí)中止任務(wù))。主要是下載請(qǐng)求用的多一些,
//普通請(qǐng)求掛起后都會(huì)重新開始請(qǐng)求。下載請(qǐng)求掛起后,
//只要不超過(guò)NSURLRequest設(shè)置的timeout時(shí)間,調(diào)用resume就是繼續(xù)請(qǐng)求。
-(void) suspend;
常見屬性:
#pragma mark ------- 獲取任務(wù)進(jìn)展 -------
//進(jìn)度,整個(gè)任務(wù)進(jìn)度的表示
@property (readonly, strong) NSProgress *progress;
//task 期望在請(qǐng)求體中發(fā)送的字節(jié)數(shù),和 Content-Length of the HTTP request 有關(guān)
@property (readonly) int64_t countOfBytesExpectedToSend;
//task 期望在響應(yīng)體中從服務(wù)器接收到的字節(jié)數(shù),通常來(lái)自HTTP響應(yīng)的內(nèi)容長(zhǎng)度標(biāo)題。
@property(readonly) int64_t countOfBytesExpectedToReceive;
// task 在響應(yīng)體中從服務(wù)器接收到的字節(jié)數(shù),實(shí)際接受的字節(jié)數(shù)。
@property (readonly) int64_t countOfBytesReceived;
// task 在請(qǐng)求體中發(fā)送給服務(wù)器的字節(jié)數(shù),實(shí)際發(fā)送的字節(jié)數(shù)。
@property (readonly) int64_t countOfBytesSent;
#pragma mark ------- 獲取常規(guī)任務(wù)信息 -------
//當(dāng)前任務(wù)的狀態(tài) :活動(dòng),暫停,正在取消或完成的過(guò)程,
//可以通過(guò)KVO的方式監(jiān)聽狀態(tài)的改變。
@property(readonly) NSURLSessionTaskState state;
//此任務(wù)的標(biāo)識(shí)符,由所屬會(huì)話分配且唯一,多個(gè)session之間可能存在相同的標(biāo)識(shí)
@property (readonly) NSUInteger taskIdentifier;
//主要用于重定向操作,用來(lái)記錄重定向前的請(qǐng)求(創(chuàng)建任務(wù)時(shí)傳遞的原始請(qǐng)求對(duì)象)
//如果這是一個(gè)流任務(wù),可能是nil
@property (nullable, readonly, copy) NSURLRequest *originalRequest;
//該任務(wù)當(dāng)前正在處理的URL請(qǐng)求對(duì)象。一般和originalRequest是一樣的,除非發(fā)生重定向才會(huì)有所區(qū)別
@property (nullable, readonly, copy) NSURLRequest *currentRequest;
//在給定會(huì)話中唯一標(biāo)識(shí)任務(wù)的標(biāo)識(shí)符
@property(readonly) NSUInteger taskIdentifier;
//服務(wù)器對(duì)當(dāng)前活動(dòng)請(qǐng)求的響應(yīng)。
@property(nullable, readonly, copy) NSURLResponse *response;
//任務(wù)描述
@property(copy) NSString *taskDescription;
//您希望主機(jī)處理任務(wù)的相對(duì)優(yōu)先級(jí),指定為0.0(最低優(yōu)先級(jí))到1.0(最高優(yōu)先級(jí))之間的浮點(diǎn)值。您可以隨時(shí)指定或更改任務(wù)的優(yōu)先級(jí),但是并非所有網(wǎng)絡(luò)協(xié)議都可以在任務(wù)啟動(dòng)后響應(yīng)更改。 沒(méi)有API可讓您從主機(jī)的角度確定任務(wù)的有效優(yōu)先級(jí)。
@property float priority;
五、AFNetWorking 的簡(jiǎn)單使用
AFNetworking4.0 是對(duì)NSURLSession的封裝,之前版本有NSURLConnection的封裝,現(xiàn)在已經(jīng)被廢棄。
簡(jiǎn)單聊一下,為啥AF要棄用之前的NSURLConnection封裝,改成對(duì)NSURLSession封裝。
首先,NSURLSession是在iOS7.0的時(shí)候蘋果推出來(lái)的。而NSURLSession又能支持Http2.0的。大家都知道Http是基于TCP協(xié)議的,早期的Http是短連接的,每次傳輸數(shù)據(jù)都需要重新連接,而每次連接的話需要進(jìn)行三次握手,這就造成了資源以及時(shí)間的浪費(fèi)。然后,在Http2.0的時(shí)候更新了Connection:keep-alive選項(xiàng),這個(gè)優(yōu)化項(xiàng),使客戶端與服務(wù)器在相同config的時(shí)候復(fù)用了同一個(gè)TCP連接,減少了每次請(qǐng)求的時(shí)間,提升了數(shù)據(jù)的傳輸速率。所以,AFNetworking也果斷的改變成對(duì)NSURLSession的封裝。
了解一下AFNetWorking 的 體系結(jié)構(gòu)
NSURLSession
主要對(duì)象NSURLSession對(duì)象進(jìn)行了進(jìn)一步的封裝,包含以下核心的類:
AFURLSessionManager
AFHTTPSessionManager
Serialization
提供了與解析數(shù)據(jù)相關(guān)
的操作接口,包含以下核心的類:
-
AFURLRequestSerialization
(請(qǐng)求序列化協(xié)議) -
AFHTTPRequestSerializer
(請(qǐng)求序列化父類)AFJSONRequestSerializer
AFPropertyListRequestSerializer
-
AFURLResponseSerialization
(響應(yīng)序列化協(xié)議) -
AFHTTPResponseSerializer
(響應(yīng)序列化父類)AFJSONResponseSerializer
AFXMLParserResponseSerializer
-
AFXMLDocumentResponseSerializer
(macOS) AFPropertyListResponseSerializer
AFImageResponseSerializer
AFCompoundResponseSerializer
AFHTTPResponseSerializer 和 AFHTTPRequestSerializer 都'符合' AFURLRequestSerialization '和' AFURLResponseSerialization '協(xié)議
Additional Functionality
額外功能
-
AFSecurityPolicy
AFSecurityPolicy 提供了與安全性相關(guān)
的操作接口 -
AFNetworkReachabilityManager
AFNetworkReachabilityManager 提供了與網(wǎng)絡(luò)狀態(tài)相關(guān)的操作接口
UIKit,提供了大量網(wǎng)絡(luò)請(qǐng)求過(guò)程中與UI界面顯示相關(guān)的操作接口,通常用于網(wǎng)絡(luò)請(qǐng)求過(guò)程中提示,使用戶交互更加友好,包含以下核心的分類/類:
AFAutoPurgingImageCache
AFImageDownloader
AFNetworkActivityIndicatorManager
UIActivityIndicatorView+AFNetworking
UIButton+AFNetworking
UIImageView+AFNetworking
UIProgressView+AFNetworking
UIRefreshControl+AFNetworking
WKWebView+AFNetworking
初始化
1、AFHttpSessonManager 繼承 自AFURLSessonManager。
2、AFHttpSessonManager 內(nèi)部實(shí)現(xiàn)并不是一個(gè)單例,而是每次都會(huì)創(chuàng)建一個(gè)新的HttpSessonManager對(duì)象。
3、其中父類(AFURLSessonManager)的初始化方法主要設(shè)置了configuration,并且通過(guò)設(shè)置maxConcurrentOperationCount = 1設(shè)置了一個(gè)串行隊(duì)列。以及解析的方式、網(wǎng)絡(luò)狀態(tài)的監(jiān)聽并創(chuàng)建了一個(gè)lock來(lái)保證線程安全等
-
1.AFHTTPSessionManager 類初始化
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// 拼接路徑
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
// 設(shè)置baseURL
self.baseURL = url;
// 請(qǐng)求序列化類
self.requestSerializer = [AFHTTPRequestSerializer serializer];
// 響應(yīng)序列化類
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
2.AFURLSessionManager 類初始化
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
// 設(shè)置初始化NSURLSessionConfiguration
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
// 設(shè)置操作隊(duì)列及控制最大并發(fā)數(shù)
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// 設(shè)置NSURLSession
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
// 解析方式,設(shè)置默認(rèn)最后解析接口返回?cái)?shù)據(jù)類(用什么方式去解析)
self.responseSerializer = [AFJSONResponseSerializer serializer];
// 設(shè)置一些服務(wù)器認(rèn)證請(qǐng)求
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
// 實(shí)時(shí)監(jiān)控當(dāng)前網(wǎng)絡(luò)狀況
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
// 加鎖 來(lái)保證線程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
// 這是為了防止后臺(tái)回來(lái),重新初始化這個(gè)session,一些之前的后臺(tái)請(qǐng)求任務(wù),導(dǎo)致程序的crash。
// 將所有回調(diào)清nil
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
請(qǐng)求方法
-
1.AFHTTPSessionManager 請(qǐng)求方法
// GET
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;
// HEAD
- (NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask * _Nonnull))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;
// POST
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
// PUT
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
// PATCH
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
// DELETE
- (NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(id)parameters
headers:(NSDictionary<NSString *,NSString *> *)headers
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
- GET:用于獲取服務(wù)器上的數(shù)據(jù),可將參數(shù)拼接在請(qǐng)求URL后面,上傳參數(shù)有限制,默認(rèn)為1024;
- HEAD:用于獲取服務(wù)器響應(yīng)首部,不包含實(shí)體部分,方便用于根據(jù)服務(wù)器響應(yīng)字段,更好地在對(duì)請(qǐng)求方式首部進(jìn)行設(shè)置,更精確得去獲取到自己所需要的數(shù)據(jù);
- POST:用于獲取服務(wù)器上的數(shù)據(jù),可將參數(shù)放在請(qǐng)求BODY中,參數(shù)個(gè)數(shù)無(wú)限制,POST相對(duì)應(yīng)GET請(qǐng)求安全一點(diǎn);
- PUT:通常用于向服務(wù)器發(fā)送請(qǐng)求,如果URL不存在,則要求服務(wù)器根據(jù)請(qǐng)求創(chuàng)建資源,如果存在,服務(wù)器就接受請(qǐng)求內(nèi)容,并修改URL資源的原始內(nèi)容;
- PATCH:請(qǐng)求服務(wù)器對(duì)資源進(jìn)行局部更新,與PUT方法的區(qū)別是,PATCH可用于更新局部資源或字段,而PUT則是更新整個(gè)資源文件,要求PUT資源一定是完整的;
- DELETE: 請(qǐng)求服務(wù)器刪除指定的資源;
-
2. AFURLSessionManager 請(qǐng)求方法
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(nullable NSData *)bodyData
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
請(qǐng)求方法的使用:
1、Creating a Download Task
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];
2、Creating an Upload Task
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Success: %@ %@", response, responseObject);
}
}];
[uploadTask resume];
Creating an Upload Task for a Multi-Part Request, with Progress
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Success: %@ %@", response, responseObject);
}
}];
[uploadTask resume];
Creating an Upload Task for a Multi-Part Request, with Progress
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionUploadTask *uploadTask;
uploadTask = [manager
uploadTaskWithStreamedRequest:request
progress:^(NSProgress * _Nonnull uploadProgress) {
// This is not called back on the main queue.
// You are responsible for dispatching to the main queue for UI updates
dispatch_async(dispatch_get_main_queue(), ^{
//Update the progress view
[progressView setProgress:uploadProgress.fractionCompleted];
});
}
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[uploadTask resume];
3、 Creating a Data Task
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://httpbin.org/get"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
請(qǐng)求 AFURLRequestSerialization
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
// 設(shè)置請(qǐng)求方式
mutableRequest.HTTPMethod = method;
// 通過(guò)監(jiān)聽用戶mutableRequest里的屬性值是否改變,
//若有則將該屬性及屬性值設(shè)置給mutableRequest
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 設(shè)置請(qǐng)求參數(shù)字段
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
//設(shè)置請(qǐng)求首部及請(qǐng)求參數(shù)字段數(shù)據(jù)body
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 用戶通過(guò)當(dāng)前類添加需要設(shè)置的首部屬性值,在該類中是將屬性值加到self.HTTPRequestHeaders中的
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
//若首部中未包含此屬性則添加該屬性到該請(qǐng)求的首部HTTPHeader中
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) { // 用戶自定義 實(shí)現(xiàn)拼接參數(shù)方法
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
} else {
switch (self.queryStringSerializationStyle) { // 默認(rèn)樣式
case AFHTTPRequestQueryStringDefaultStyle:
// 默認(rèn)樣式進(jìn)行將字典轉(zhuǎn)換后的參數(shù)拼接字符串
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// self.HTTPMethodsEncodingParametersInURI包含@"GET", @"HEAD", @"DELETE",使用拼接在url后面
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
//URL.query判斷是否已經(jīng)有數(shù)據(jù)參數(shù),若有則直接用&拼接,否則用?拼接
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
// POST請(qǐng)求設(shè)置body的默認(rèn)編碼方式
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
// 用NSUTF8StringEncoding編碼格式將query參數(shù)字符串轉(zhuǎn)為NSData
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
手動(dòng)監(jiān)聽mutableRequest中網(wǎng)絡(luò)請(qǐng)求屬性的最新值,若出現(xiàn)了改變,則實(shí)時(shí)更新并設(shè)置到mutableRequest中;
將用戶設(shè)置的請(qǐng)求首部設(shè)置給mutableRequest的HTTPHeaderField;
將請(qǐng)求參數(shù)進(jìn)行拼接設(shè)置給mutableRequest的HTTPBody;通過(guò)對(duì)mutableRequest的相關(guān)屬性設(shè)置參數(shù)值后,將返回的mutableRequest生成任務(wù)Task并開啟任務(wù),此時(shí)一個(gè)請(qǐng)求任務(wù)啟動(dòng),等待網(wǎng)絡(luò)請(qǐng)求結(jié)果!
請(qǐng)求回調(diào)代理
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 將delegate與manage之間進(jìn)行關(guān)聯(lián)
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// 數(shù)據(jù)請(qǐng)求完成時(shí)回調(diào)AFURLSessionManagerTaskDelegate的代理方法
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
// AFURLSessionManagerTaskDelegate的代理方法
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
__strong AFURLSessionManager *manager = self.manager;
// 省略部分代碼
if (error) { // 出錯(cuò)時(shí)的處理
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
});
} else { // 未出錯(cuò)時(shí)的處理
dispatch_async(url_session_manager_processing_queue(), ^{
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
}
}
完成數(shù)據(jù)請(qǐng)求時(shí),會(huì)回調(diào)didCompleteWithError對(duì)數(shù)據(jù)進(jìn)行相關(guān)處理,這里若manager設(shè)置了跟AFURLSessionManagerTaskDelegate關(guān)聯(lián)起來(lái),則在任務(wù)完成回調(diào)后,將調(diào)用AFURLSessionManagerTaskDelegate類中的方法對(duì)數(shù)據(jù)進(jìn)行處理,數(shù)據(jù)處理的方式按照responseSerializer對(duì)應(yīng)設(shè)置的類型方式進(jìn)行!
響應(yīng)處理AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 省略部分代碼
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
NSError *serializerError = nil;
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
return responseObject;
}
}
return [super responseObjectForResponse:response data:data error:error];
}
AFSecurityPolicy安全認(rèn)證類
隨著互聯(lián)網(wǎng)的快速發(fā)展,導(dǎo)致人們的隱私也變得越來(lái)越重要了!蘋果爸爸一直都很注重用戶隱私問(wèn)題,所以也提倡應(yīng)更縝密地傳輸用戶數(shù)據(jù)信息,防止數(shù)據(jù)泄露!所以在這里也引申出了HTTPS這個(gè)概念,HTTPS具體原理可以參考我寫的文章 HTTPS協(xié)議:叫個(gè)外賣咋這么復(fù)雜呢!!
- 蘋果認(rèn)證過(guò)程
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
// 自定義處理證書方式
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 判斷服務(wù)器返回的證書是否是服務(wù)器信任的
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { // 通過(guò)認(rèn)證
// 若驗(yàn)證通過(guò),生成用NSURLCredential類生成證書
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
// NSURLSessionAuthChallengeUseCredential為使用正式
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
// 無(wú)證書時(shí),忽略證書,這是系統(tǒng)默認(rèn)的做法
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else { // 證書未通過(guò)認(rèn)證,直接取消認(rèn)證,忽略證書
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
// 忽略證書,系統(tǒng)默認(rèn)處理方式
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
// 使用completionHandler回調(diào),讓系統(tǒng)去對(duì)接完成服務(wù)器端的認(rèn)證過(guò)程
completionHandler(disposition, credential);
}
}
實(shí)現(xiàn)HTTPS認(rèn)證過(guò)程中,我們可以使用證書或非證書認(rèn)證的方式,這個(gè)具體看公司需求,下面我們來(lái)看看這個(gè)具體過(guò)程!
CA機(jī)構(gòu)認(rèn)證證書
:這種方式可以讓客戶端開發(fā)感覺(jué)很爽,只需要將請(qǐng)求URL中的http換成https后,便可以放心地進(jìn)行數(shù)據(jù)傳輸,因?yàn)榉?wù)器端只需要將用于認(rèn)證的加密證書配置為CA機(jī)構(gòu)信任認(rèn)證的證書即可,這樣服務(wù)器在第一次加密認(rèn)證時(shí),用認(rèn)證證書加密服務(wù)器公鑰發(fā)送給客戶端時(shí),當(dāng)加密數(shù)據(jù)到達(dá)客戶端后,客戶端自帶的CA機(jī)構(gòu)證書會(huì)認(rèn)證服務(wù)器加密時(shí)使用的加密證書,若驗(yàn)證成功后,客戶端便可取出服務(wù)器用于客戶端傳輸數(shù)據(jù)到服務(wù)器所使用的公鑰!
非CA機(jī)構(gòu)認(rèn)證證書
:這個(gè)過(guò)程一般是使用服務(wù)器端配置的證書,而這個(gè)證書并非CA機(jī)構(gòu)認(rèn)證的,但我們可以先將證書給客戶端,讓客戶端去認(rèn)可這個(gè)配置的證書,完成后面的HTTPS驗(yàn)證過(guò)程,若驗(yàn)證通過(guò)則雙方可進(jìn)行交換數(shù)據(jù)!
-
認(rèn)證過(guò)程
1、
當(dāng)客戶端需要對(duì)服務(wù)器發(fā)送到的證書進(jìn)行認(rèn)證時(shí),會(huì)調(diào)用didReceiveChallenge代理方法
;-
2、
若證書是否是服務(wù)器所信任的,則通過(guò)三種認(rèn)證模式其中的一種進(jìn)行認(rèn)證
:2.1、AFSSLPinningModeNone:驗(yàn)證是否允許非權(quán)威證書(自簽名)或是認(rèn)證機(jī)構(gòu)信任的證書,是的話則驗(yàn)證通過(guò);
2.2、AFSSLPinningModeCertificate:獲取客戶端證書鏈中是否有證書包含服務(wù)器端證書鏈中的證書,是的話則驗(yàn)證通過(guò);
2.3、AFSSLPinningModePublicKey:從客戶端證書鏈中將證書轉(zhuǎn)換為公鑰key,服務(wù)器端證書鏈也一樣轉(zhuǎn)換成公鑰key,通過(guò)各自的公鑰key進(jìn)行判斷,若相等則通過(guò)驗(yàn)證;
-
若證書驗(yàn)證通過(guò),則生成NSURLCredential證書,使用completionHandler回調(diào),讓系統(tǒng)去對(duì)接完成服務(wù)器端的認(rèn)證過(guò)程
;
AFNetworkReachabilityManager實(shí)時(shí)監(jiān)控
AFNetworkReachabilityManager是AFN提供的可以對(duì)網(wǎng)絡(luò)狀態(tài)進(jìn)行檢測(cè),并在主線程完成監(jiān)控狀態(tài)的回調(diào)!蘋果對(duì)需要聯(lián)網(wǎng)的應(yīng)用要求很高,就是必須要進(jìn)行聯(lián)網(wǎng)檢查。另外,當(dāng)網(wǎng)絡(luò)發(fā)生異常時(shí)能夠及時(shí)提示用戶網(wǎng)絡(luò)已斷開,而不是程序問(wèn)題造成卡頓;
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
// 一共有四種狀態(tài)
switch (status) {
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"AFNetworkReachability Not Reachable");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"AFNetworkReachability Reachable is WWAN");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"AFNetworkReachability Reachable is WiFi");
break;
case AFNetworkReachabilityStatusUnknown:
default:
NSLog(@"AFNetworkReachability Unknown");
break;
}
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
當(dāng)我們使用單例sharedManager簡(jiǎn)單地使用startMonitoring開啟網(wǎng)絡(luò)檢測(cè),可以實(shí)時(shí)檢測(cè)到網(wǎng)絡(luò)狀態(tài),但是存在一點(diǎn)不足之處
:
但是這里并不能檢測(cè)到服務(wù)器是否真的可達(dá),只能檢測(cè)設(shè)備是否連接到局域網(wǎng),以及用的WiFi還是WWAN。即使把設(shè)備網(wǎng)絡(luò)關(guān)了,立馬檢測(cè)出NotReachable,連接到路由器立馬檢測(cè)出的還是可以連接到WIFI !有時(shí)候雖然聯(lián)網(wǎng)了,但是網(wǎng)絡(luò)不一定就能上網(wǎng)是一種可能性,另一種可能性是數(shù)據(jù)包在傳輸過(guò)程中可以會(huì)受影響而被丟棄,畢竟數(shù)據(jù)包在網(wǎng)際層傳輸,每經(jīng)過(guò)一個(gè)路由器默認(rèn)經(jīng)過(guò)一跳,但達(dá)到255跳時(shí),路由器就會(huì)自動(dòng)丟包!
參考文獻(xiàn):NSURLSession.h
蘋果官網(wǎng) NSURLSession
http://www.lxweimin.com/p/ac79db251cbf
https://blog.csdn.net/jbr5740/article/details/89598766