iOS 網(wǎng)絡(luò)框架-AFNetworking 3.1.0 源碼解讀

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 請求的流程圖,大概可以分為這幾個步驟,我會逐個解讀這個流程。


Get請求流程圖

AFHTTPSessionManager 發(fā)起Get請求

發(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)建 NSURLSessionDataTask 對象實例并返回這個實例。首先創(chuàng)建一個 NSMutableURLRequest 對象的實例,然后配置。之后是使用 NSMutableURLRequest 對象的實例創(chuàng)建 NSURLSessionDataTask 對象實例,然后配置,可以選擇性地傳入各類 Block 回調(diào),用于監(jiān)聽網(wǎng)絡(luò)請求的進度比如上傳進度,下載進度,請求成功,請求失敗。

配置 NSMutableURLRequest 對象

配置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;
}```


![配置NSMutableURLRequest對象](http://upload-images.jianshu.io/upload_images/656644-a228e54cc99ab038.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
先設(shè)置 HTTP header,之后格式化請求參數(shù),設(shè)置參數(shù)的編碼類型。這個是這個方法的基本操作流程。對于 Get 操作來說,參數(shù)是直接拼接在請求地址后面。

### 配置 NSURLSessionDataTask 對象

![配置NSURLSessionDataTask對象](http://upload-images.jianshu.io/upload_images/656644-df139131c05cc9b8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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


![給NSURLSessionDataTask對象實例設(shè)置Delegate](http://upload-images.jianshu.io/upload_images/656644-d75418e6972979c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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 的代理,

NSURLSessionTask設(shè)置進度回調(diào)

設(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)

NSURLSessionDelegate方法
NSURLSessionTaskDelegate方法

AFN 里面有關(guān) NSURLSessionDelegate 的回調(diào)方法非常的多,這里我們只調(diào)和
NSURLSessionTask 相關(guān)的部分方法和 KVO 處理來進行說明,其他的大家可以參考源碼細看。

KVO監(jiān)聽請求過程
收到響應(yīng)數(shù)據(jù)
請求完成

對于我們的 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];
}
cleanUpProgressForTask
removeNotificationObserverForTask

關(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 請求

![HTTPS認證1](http://upload-images.jianshu.io/upload_images/656644-caee53a1c035de81.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


![HTTPS認證2](http://upload-images.jianshu.io/upload_images/656644-808700367b4530de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


```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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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