AFNetworking 基本是 iOS 開發(fā)中的網(wǎng)絡(luò)第三方庫標(biāo)配,本文基于 AFNetworking3.1.0 版本。廢話不多說,這篇文章主要從使用的角度來介紹 AFNetworking 的發(fā)起 Get 請求的過程,偏重于解讀過程,解讀當(dāng)你使用 AFNetworking 發(fā)起一個 Get 請求的時候,AFNetworking 內(nèi)部的處理過程。而不是對 AFNetworking 源代碼的各個類的代碼進行深入解析,在源碼深度解析方面,網(wǎng)絡(luò)上已經(jīng)有很多不錯的文章,在文章的末尾我會給出參考鏈接。
Get 請求流程圖
這是 AFNetworking 發(fā)起一個 Get 請求的流程圖,大概可以分為這幾個步驟,我會逐個解讀這個流程。
AFHTTPSessionManager 發(fā)起Get請求
這個方法是 AFN 的 Get 請求的起點,其他 Get 請求的方法也都是直接或者間接調(diào)用這個方法來發(fā)起 Get 請求。這個方法的代碼量很少也很直觀,就是調(diào)用其他方法生成 NSURLSessionDataTask 對象的實例,然后調(diào)用 NSURLSessionDataTask 的 resume 方法發(fā)起請求。
創(chuàng)建 NSURLSessionDataTask
這個方法是創(chuàng)建 NSURLSessionDataTask 對象實例并返回這個實例。首先創(chuàng)建一個 NSMutableURLRequest 對象的實例,然后配置。之后是使用 NSMutableURLRequest 對象的實例創(chuàng)建 NSURLSessionDataTask 對象實例,然后配置,可以選擇性地傳入各類 Block 回調(diào),用于監(jiān)聽網(wǎng)絡(luò)請求的進度比如上傳進度,下載進度,請求成功,請求失敗。
配置 NSMutableURLRequest 對象
在這個方法中先使用了url 創(chuàng)建了一個 NSMutableURLRequest 對象的實例,并且設(shè)置了 HTTPMethod 為 Get 方法(如果是 Post 方法,那么這里就是設(shè)置 Post 方法,以此類推)然后使用 KVC 的方法設(shè)置了 NSMutableURLRequest 的一些屬性。
//設(shè)置 NSMutableURLRequest 的屬性
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//allowsCellularAccess 允許使用數(shù)據(jù)流量
//cachePolicy 緩存策略
//HTTPShouldHandleCookies 處理Cookie
//HTTPShouldUsePipelining 批量請求
//networkServiceType 網(wǎng)絡(luò)狀態(tài)
//timeoutInterval 超時
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}```

先設(shè)置 HTTP header,之后格式化請求參數(shù),設(shè)置參數(shù)的編碼類型。這個是這個方法的基本操作流程。對于 Get 操作來說,參數(shù)是直接拼接在請求地址后面。
### 配置 NSURLSessionDataTask 對象

之后配置 NSMutableURLRequest 對象就需要配置 NSURLSessionDataTask 對象了。主要分為 2 個步驟,第一個步驟是創(chuàng)建 NSURLSessionDataTask 對象實例,第二個步驟是給 NSURLSessionDataTask 對象實例設(shè)置 Delegate。用于實時了解網(wǎng)絡(luò)請求的過程。

AFN 的代理統(tǒng)一使用 AFURLSessionManagerTaskDelegate 對象來管理,使用AFURLSessionManagerTaskDelegate 對象來接管 NSURLSessionTask 網(wǎng)絡(luò)請求過程中的回調(diào),然后再傳入 AFN 內(nèi)部進行管理。
```CPP
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
如代碼所示 AFURLSessionManagerTaskDelegate 接管了 NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 的各種回調(diào),然后做內(nèi)部處理。這也是第三方網(wǎng)絡(luò)請求框架的重點,讓網(wǎng)絡(luò)請求更加易用,好用。
//通過task的標(biāo)識符管理代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
通過 NSURLSessionTask 的 taskIdentifier 標(biāo)識符對 delegate 進行管理,只要是用于識別該NSURLSessionTask 的代理,
設(shè)置各類回調(diào) Block,給 NSURLSessionTask 使用 KVO 進行各種過程進度監(jiān)聽。
//給task添加暫停和恢復(fù)的通知
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
監(jiān)聽 NSURLSessionTask 被掛起和恢復(fù)的通知
網(wǎng)絡(luò)請求開始
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
當(dāng) NSURLSessionTask 創(chuàng)建和配置完畢之后,它并不會主動執(zhí)行,而是需要我們主動調(diào)用 resume方法,NSURLSessionTask 才會開始執(zhí)行。
網(wǎng)絡(luò)請求回調(diào)
AFN 里面有關(guān) NSURLSessionDelegate 的回調(diào)方法非常的多,這里我們只調(diào)和
NSURLSessionTask 相關(guān)的部分方法和 KVO 處理來進行說明,其他的大家可以參考源碼細看。
對于我們的 Get 請求來說,我們最關(guān)注的莫過于關(guān)注請求過程進度,收到響應(yīng)數(shù)據(jù)和請求完成這2個回調(diào)。
//KVO監(jiān)聽的屬性值發(fā)生變化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
NSLog(@"countOfBytesReceived");
//這個是在Get請求下,網(wǎng)絡(luò)響應(yīng)過程中已經(jīng)收到的數(shù)據(jù)量
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已經(jīng)收到
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
NSLog(@"countOfBytesExpectedToReceive");
//這個是在Get請求下,網(wǎng)絡(luò)響應(yīng)過程中期待收到的數(shù)據(jù)量
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待收到
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
NSLog(@"countOfBytesSent");
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已經(jīng)發(fā)送
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
NSLog(@"countOfBytesExpectedToSend");
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待發(fā)送
}
}
else if ([object isEqual:self.downloadProgress]) {
//下載進度變化
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
//上傳進度變化
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
//收到請求響應(yīng)
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
NSLog(@"收到請求響應(yīng)");
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;//允許繼續(xù)加載
//是否有收到請求響應(yīng)的回調(diào)Block
if (self.dataTaskDidReceiveResponse) {
//若有調(diào)用該Block
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
//是否有請求響應(yīng)完成的回調(diào)Block
if (completionHandler) {
//若有調(diào)用該Block
completionHandler(disposition);
}
}
//請求完成
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
NSLog(@"請求完成");
//取出該NSURLSessionTask的代理對象
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
//若是該代理對象存在,那么將對應(yīng)數(shù)據(jù)轉(zhuǎn)給該代理對象處理
[delegate URLSession:session task:task didCompleteWithError:error];
//NSURLSessionTask任務(wù)完成之后,移除該NSURLSessionTask的代理對象
[self removeDelegateForTask:task];
}
//是否有請求完成的回調(diào)Block
if (self.taskDidComplete) {
//若有調(diào)用改Block
self.taskDidComplete(session, task, error);
}
}
因為在配置 NSURLSessionDataTask 對象的時候我們有給 NSURLSessionTask 做了一系列配置,那么當(dāng) NSURLSessionDataTask 任務(wù)完成之后,我們需要將該 NSURLSessionDataTask 的一系列配置全部清理掉。
這個是我們的配置過程
//通過task的標(biāo)識符管理代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
那么對應(yīng)的清理過程是這樣的,就是設(shè)置過程中做了什么,在清理過程中就需要去掉什么。
//給task移除delegate
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
[self.lock lock];
[delegate cleanUpProgressForTask:task];
[self removeNotificationObserverForTask:task];
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
}
關(guān)于 Post 請求
#pragma mark - AFURLRequestSerialization
//設(shè)置Header和請求參數(shù)
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
//判斷header的field是否存在,如果不存在則設(shè)置,存在則跳過
if (![request valueForHTTPHeaderField:field]) {
//設(shè)置 header
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
//用傳進來的自定義block格式化請求參數(shù)
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//默認的格式化方式
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//判斷是否是GET/HEAD/DELETE方法, 對于GET/HEAD/DELETE方法,把參數(shù)加到URL后面
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
//判斷是否有參數(shù)
if (query && query.length > 0) {
//拼接請求參數(shù)
NSLog(@"query-->%@",query);
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
//參數(shù)帶在body上,大多是POST PUT
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
//設(shè)置Content-Type HTTP頭,告訴服務(wù)端body的參數(shù)編碼類型
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}```
如果是 Post 請求,那么請求參數(shù)是沒有拼接在 URL 上面,而是放在 body 上,這個是 Post 和 Get 請求的最大區(qū)別了,其他過程和 Get 請求并沒有太多區(qū)別。
## 關(guān)于 HTTPS 請求


```objectivec
//Http認證處理
//認證處理
/*
*http://www.cnblogs.com/polobymulberry/p/5140806.html
*web服務(wù)器接收到客戶端請求時,有時候需要先驗證客戶端是否為正常用戶,再決定是夠返回真實數(shù)據(jù)。
*這種情況稱之為服務(wù)端要求客戶端接收挑戰(zhàn)(NSURLAuthenticationChallenge *challenge)。
*接收到挑戰(zhàn)后,
*客戶端要根據(jù)服務(wù)端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential
*(disposition指定應(yīng)對這個挑戰(zhàn)的方法,而credential是客戶端生成的挑戰(zhàn)證書,注意只有challenge中認證方法為NSURLAuthenticationMethodServerTrust的時候,才需要生成挑戰(zhàn)證書)。
*最后調(diào)用completionHandler回應(yīng)服務(wù)器端的挑戰(zhàn)。
*/
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//NSURLAuthenticationChallenge 挑戰(zhàn)處理類型為 默認
/*
*NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理
*NSURLSessionAuthChallengeUseCredential:使用指定的證書
*NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
//自定義方法,用來如何應(yīng)對服務(wù)器端的認證挑戰(zhàn)
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
//服務(wù)端要求客戶端提供證書
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//客戶端評估服務(wù)端的安全性
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//客戶端產(chǎn)生證書
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
//使用指定的證書
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
//默認處理
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//不處理服務(wù)端的認證要求
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
//如果沒有實現(xiàn)方法
/*
*- (void)URLSession:(NSURLSession *)session
*didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
*completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
*/
//那么URLSession會調(diào)用下面的方法進入認證處理
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.taskDidReceiveAuthenticationChallenge) {
disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
如果是 HTTPS 請求的話,那么會先走上面的2個代理方法進行 HTTPS 認證,之后繼續(xù)其他操作。
總結(jié)
AFN 發(fā)起 Get 請求主要分為以下步驟:
1.創(chuàng)建 NSURLSessionDataTask
2.配置 NSURLSessionDataTask
3.設(shè)置 NSURLSessionDataTask 的 Delegate
4.調(diào)用 NSURLSessionDataTask 的 resume 方法開始請求
5.在 Delegate 的方法里面處理網(wǎng)絡(luò)請求的各個過程
6.清理 NSURLSessionDataTask 的配置
其實也就是使用 NSURLSessionDataTask 的步驟,AFN 在這幾個步驟加了一些封裝,讓整個請求過程更加好用,易用。
對于 AFN 這類幾乎是 iOS 開發(fā)網(wǎng)絡(luò)庫標(biāo)配的開源項目來說,肯定已經(jīng)有許多非常優(yōu)秀的源碼解析文章了。所以這篇文章是著重講解和介紹 AFN 的整個網(wǎng)絡(luò)請求的處理流程而且很多的技術(shù)細節(jié)。相信如果對流程熟悉的話,那么要想找對應(yīng)的細節(jié)處理過程也就比較簡單的,再配合一些調(diào)試手段的話,基本上對于 AFN 的細節(jié)處理的理解也就不再話下了。由于個人水平有限,文章有不對之處懇請指出,我稍作修改,大家共同進步。
參考資料
http://blog.cnbang.net/tech/2320/
http://blog.cnbang.net/tech/2371/
http://blog.cnbang.net/tech/2416/
http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
http://bugly.qq.com/bbs/forum.php?
http://www.guokr.com/post/114121/mod=viewthread&tid=417&fromuid=6
http://www.guokr.com/post/116169/
http://www.guokr.com/blog/148613/
http://www.cnblogs.com/hyddd/archive/2009/01/07/1371292.html
http://www.cnblogs.com/polobymulberry/p/5140806.html
https://github.com/AFNetworking/AFNetworking/tree/3.1.0