1. 程式人生 > >iOS網路框架AFNetworking3.1.0底層原始碼解讀

iOS網路框架AFNetworking3.1.0底層原始碼解讀

AFNetworking基本是iOS開發中的網路第三方庫標配,本文基於AFNetworking3.1.0版本。咱們主要從使用的角度來介紹AFNetworking的發起Get請求的過程,偏重於解讀過程,解讀當你使用AFNetworking發起一個Get請求的時候,AFNetworking內部的處理過程。而不是對AFNetworking原始碼的各個類的程式碼進行深入解析,在原始碼深度解析方面,網路上已經有很多不錯的文章,在文章的末尾我會給出參考連結。

發起請求流程圖get為例
AFNetworking發起一個Get請求的流程圖,大概可以分為這幾個步驟,我會逐個解讀這個流程。

Get請求流程圖Get請求流程圖

1.AFHTTPSessionManager發起Get請求

發起Get請求發起Get請求

這個方法是AFNetworking的Get請求的起點,其他Get請求的方法也都是直接或者間接呼叫這個方法來發起Get請求。這個方法的程式碼量很少也很直觀,就是呼叫其他方法生成NSURLSessionDataTask物件的例項,然後呼叫NSURLSessionDataTask的resume方法發起請求。

2.建立NSURLSessionDataTask

建立NSURLSessionDataTask建立NSURLSessionDataTask

這個方法是建立NSURLSessionDataTask物件例項並返回這個例項。首先建立一個NSMutableURLRequest物件的例項,然後配置。之後是使用NSMutableURLRequest物件的例項建立NSURLSessionDataTask物件例項,然後配置,可以選擇性地傳入各類Block回撥,用於監聽網路請求的進度比如上傳進度,下載進度,請求成功,請求失敗。

3.配置NSMutableURLRequest物件

配置NSMutableURLRequest物件配置NSMutableURLRequest物件

在這個方法中先使用了url建立了一個NSMutableURLRequest物件的例項,並且設定了HTTPMethod為Get方法(如果是Post方法,那麼這裡就是設定Post方法,以此類推)然後使用KVC的方法設定了NSMutableURLRequest的一些屬性。

//設定NSMutableURLRequest的屬性
 static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
    static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil
; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //allowsCellularAccess 允許使用資料流量 //cachePolicy 快取策略 //HTTPShouldHandleCookies 處理Cookie //HTTPShouldUsePipelining 批量請求 //networkServiceType 網路狀態 //timeoutInterval 超時 _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; }); return _AFHTTPRequestSerializerObservedKeyPaths; }

配置NSMutableURLRequest物件配置NSMutableURLRequest物件

先設定HTTP header,之後格式化請求引數,設定引數的編碼型別。這個是這個方法的基本操作流程。對於Get操作來說,引數是直接拼接在請求地址後面。

4.配置NSURLSessionDataTask物件

配置NSURLSessionDataTask物件配置NSURLSessionDataTask物件

之後配置NSMutableURLRequest物件就需要配置NSURLSessionDataTask物件了。主要分為2個步驟,第一個步驟是建立NSURLSessionDataTask物件例項,第二個步驟是給NSURLSessionDataTask物件例項設定Delegate。用於實時瞭解網路請求的過程。

給NSURLSessionDataTask物件例項設定Delegate給NSURLSessionDataTask物件例項設定Delegate

AFN的代理統一使用AFURLSessionManagerTaskDelegate物件來管理,使用AFURLSessionManagerTaskDelegate物件來接管NSURLSessionTask網路請求過程中的回撥,然後再傳入AFN內部進行管理。

@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>

如程式碼所示AFURLSessionManagerTaskDelegate接管了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate的各種回撥,然後做內部處理。這也是第三方網路請求框架的重點,讓網路請求更加易用,好用。

//通過task的識別符號管理代理
- (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識別符號對delegate進行管理,只要是用於識別該NSURLSessionTask的代理

NSURLSessionTask設定進度回撥NSURLSessionTask設定進度回撥

設定各類回撥Block,給NSURLSessionTask使用KVO進行各種過程進度監聽。

//給task新增暫停和恢復的通知
- (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];
}

監聽NSURLSessionTask被掛起和恢復的通知

- (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;
}

當NSURLSessionTask建立和配置完畢之後,它並不會主動執行,而是需要我們主動呼叫resume方法,NSURLSessionTask才會開始執行。

網路請求回撥

NSURLSessionDelegate方法NSURLSessionDelegate方法
NSURLSessionTaskDelegate方法NSURLSessionTaskDelegate方法

AFN裡面有關NSURLSessionDelegate的回撥方法非常的多,這裡我們只調和NSURLSessionTask相關的部分方法和KVO處理來進行說明,其他的大家可以參考原始碼細看。

KVO監聽請求過程KVO監聽請求過程
收到響應資料收到響應資料
請求完成請求完成

對於我們的Get請求來說,我們最關注的莫過於關注請求過程進度,收到響應資料和請求完成這2個回撥。

//KVO監聽的屬性值發生變化
- (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請求下,網路響應過程中已經收到的資料量
            self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已經收到
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
              NSLog(@"countOfBytesExpectedToReceive");
//這個是在Get請求下,網路響應過程中期待收到的資料量
            self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待收到
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
             NSLog(@"countOfBytesSent");
            self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//已經發送
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
              NSLog(@"countOfBytesExpectedToSend");
            self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];//期待發送
        }
    }
    else if ([object isEqual:self.downloadProgress]) {
        //下載進度變化
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        //上傳進度變化
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}
//收到請求響應
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    NSLog(@"收到請求響應");
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;//允許繼續載入

//是否有收到請求響應的回撥Block
    if (self.dataTaskDidReceiveResponse) {
//若有呼叫該Block
        disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
    }
//是否有請求響應完成的回撥Block
    if (completionHandler) {
//若有呼叫該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) {
//若是該代理物件存在,那麼將對應資料轉給該代理物件處理
        [delegate URLSession:session task:task didCompleteWithError:error];
//NSURLSessionTask任務完成之後,移除該NSURLSessionTask的代理物件
        [self removeDelegateForTask:task];
    }
//是否有請求完成的回撥Block
    if (self.taskDidComplete) {
//若有呼叫改Block
        self.taskDidComplete(session, task, error);
    }
}

因為在配置NSURLSessionDataTask物件的時候我們有給NSURLSessionTask做了一系列配置,那麼當NSURLSessionDataTask任務完成之後,我們需要將該NSURLSessionDataTask的一系列配置全部清理掉。

這個是配置過程

//通過task的識別符號管理代理
- (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];
}

那麼對應的清理過程是這樣的,就是設定過程中做了什麼,在清理過程中就需要去掉什麼。

//給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];
}

cleanUpProgressForTaskcleanUpProgressForTask
removeNotificationObserverForTaskremoveNotificationObserverForTask

關於Post請求

請求序列化方法請求序列化方法

#pragma mark - AFURLRequestSerialization
//設定Header和請求引數
- (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是否存在,如果不存在則設定,存在則跳過
        if (![request valueForHTTPHeaderField:field]) {
            //設定 header
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    NSString *query = nil;
    if (parameters) {
        //用傳進來的自定義block格式化請求引數
        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方法,把引數加到URL後面
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
       //判斷是否有引數
        if (query && query.length > 0) {
            //拼接請求引數
            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 = @"";
        }
        //引數帶在body上,大多是POST PUT
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            //設定Content-Type HTTP頭,告訴服務端body的引數編碼型別
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

如果是Post請求,那麼請求引數是沒有拼接在URL上面,而是放在body上,這個是Post和Get請求的最大區別了,其他過程和Get請求並沒有太多區別。

關於HTTPS請求

HTTPS認證1HTTPS認證1

HTTPS認證2HTTPS認證2

//Http認證處理
//認證處理
/*
 *http://www.cnblogs.com/polobymulberry/p/5140806.html
 *web伺服器接收到客戶端請求時,有時候需要先驗證客戶端是否為正常使用者,再決定是夠返回真實資料。
 *這種情況稱之為服務端要求客戶端接收挑戰(NSURLAuthenticationChallenge *challenge)。
 *接收到挑戰後,
 *客戶端要根據服務端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential
 *(disposition指定應對這個挑戰的方法,而credential是客戶端生成的挑戰證書,注意只有challenge中認證方法為NSURLAuthenticationMethodServerTrust的時候,才需要生成挑戰證書)。
 *最後呼叫completionHandler迴應伺服器端的挑戰。
 */
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //NSURLAuthenticationChallenge 挑戰處理型別為 預設
    /*
     *NSURLSessionAuthChallengePerformDefaultHandling:預設方式處理
     *NSURLSessionAuthChallengeUseCredential:使用指定的證書
     *NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    //自定義方法,用來如何應對伺服器端的認證挑戰
    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        //服務端要求客戶端提供證書
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            //客戶端評估服務端的安全性
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                //客戶端產生證書
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    //使用指定的證書
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    //預設處理
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                //不處理服務端的認證要求
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}
//如果沒有實現方法
/*
 *- (void)URLSession:(NSURLSession *)session
 *didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 *completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
 */
//那麼URLSession會呼叫下面的方法進入認證處理
- (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認證,之後繼續其他操作。

總結
AFN發起Get請求主要分為以下步驟:
1.建立NSURLSessionDataTask
2.配置NSURLSessionDataTask
3.設定NSURLSessionDataTask的Delegate
4.呼叫NSURLSessionDataTask的resume方法開始請求
5.在Delegate的方法裡面處理網路請求的各個過程
6.清理NSURLSessionDataTask的配置
其實也就是使用NSURLSessionDataTask的步驟,AFN在這幾個步驟加了一些封裝,讓整個請求過程更加好用,易用。