AFNetworking源碼簡析

AFNetworking基本是蘋果開發中網絡請求庫的標配,它是一個輕量級的網絡庫,專門針對iOSOS X的網絡應用設計,具有模塊化的架構和豐富的APIs接口,功能強大並且使用簡單,深受蘋果應用開發人員的喜愛。

本文主要介紹一下AFNetworking(版本:3.1.0)的模塊結構、請求的執行過程、網絡狀態監測以及網絡安全的處理等等,從而對AFNetworking的具體功能、執行過程有一個大致的瞭解,在實際的項目開發過程中,能夠更好的進行應用。

一、結構

下面是AFNetworking的源碼結構圖,主要分爲:NSURLSession核心代碼、ReachabilitySecuritySerializationUIKit5部分。

AFNetworking的源碼結構圖:


圖1

AFHTTPSessionManager的依賴關係:


圖2

AFNetworking的網絡數據的序列化(Serialization)的類結構圖:


圖3

NSURLSessionTask的類結構圖:


圖4

各部分功能介紹如下:

1.NSURLSession

這是網絡請求的核心代碼,承擔發送請求、接收數據、異常處理等功能,以及請求和響應的數據序列化功能

2.Reachability

用來監控設備的網絡狀態,只能識別WWANWiFi這兩種網絡

3.Security

網絡安全的設計,尤其是利用https增強數據安全性

4.Serialization

請求和響應數據的序列化處理

5.UIKit

主要是對一些常用UI控件做網絡交互方便的擴展

二、應用

1.網絡狀態監測

網絡狀態監測,通過AFNetworkReachabilityManager的單例對象設置網絡狀態變化的block,然後通過AFStringFromNetworkReachabilityStatus把當前網絡狀態轉換爲對應的字符串。一般情況下,設置網絡監測的代碼放到AppDelegate.m裏,監測整個項目的網絡狀態變化。

[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
    NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
}];

[[AFNetworkReachabilityManager sharedManager] startMonitoring];

2.網絡安全

HTTPS相比HTTP提高了通信的安全性,而蘋果也建議網絡請求使用HTTPS,所以有必要了解一下HTTPS的工作過程(HTTPS通信流程介紹)。而AFNetworking中的
AFSecurityPolicy類用來驗證證書是否有效,從而防止被中間人攻擊。在實際開發中,會在AFURLSessionManager類的初始化方法裏使用默認的安全設置:

  • 不允許無效或過期的證書
  • 驗證domain名稱
  • 不對證書和公鑰進行驗證
// AFURLSessionManager.m
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    ...
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
    ...
}
// AFSecurityPolicy.m
+ (instancetype)defaultPolicy {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = AFSSLPinningModeNone;

    return securityPolicy;
}

部分重要屬性介紹如下:

@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;

a、AFSSLPinningModeNone:不驗證服務器的整數,完全信任

b、AFSSLPinningModePublicKey:驗證服務器返回的證書中的PublicKey

c、AFSSLPinningModeCertificate:把服務器的返回的證書和本地證書進行驗證

@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;

這個屬性保存着所有的可用做校驗的證書的集合

@property (nonatomic, assign) BOOL allowInvalidCertificates;

是否允許無效或過期的證書,默認是不允許

@property (nonatomic, assign) BOOL validatesDomainName;

是否驗證證書中的域名domain,默認是驗證

3.HTTP請求

HTTP請求在開發中常用的就是GETPOST這兩種類型,針對這兩種請求方式的區別請參考HTTP協議格式詳解。本文從源碼實現的角度,不會詳細介紹各個類的功能及依賴關係,只是面向應用的角度來分析一下這兩種請求如何實現的:

整個網絡協議的流程圖:

圖5

GET/POST

AFNetworking發起的網絡請求,GETPOST的差異就是在設置請求的URLHTTPBody時的差異。下面根據上面的流程圖詳細介紹一下具體的代碼實現:

步驟一:發起網絡請求

  • 通過requestSerializer.timeoutInterval設置請求超時時間
  • 通過responseSerializer.acceptableContentTypes設置客戶端可接收的數據類型
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

    manager.requestSerializer.timeoutInterval = 10;
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", nil];

    [manager GET:@"https://app.chaoaicai.com/api/todayApi/discoveryInfo.app" parameters:@{@"name": @"kelvin"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"%@", responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@", error);
    }];

步驟二、三:創建NSMutableURLRequest 和 NSURLSessionDataTask

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    ...
    // 創建Request
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    ...
    // 創建dataTask
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        ...
    }];
    ...
}

步驟四:設置AFURLSessionManager的代理

- (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 {

    __block NSURLSessionDataTask *dataTask = nil;
    // 創建dataTask
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });
    // 設置dataTask的代理
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

步驟五:調用NSURLSessionDataTask的resume方法開始網絡請求

通過調用NSURLSessionTaskresume來啓動任務,也可以調用suspend來掛起任務

- (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
{

    ...
    [dataTask resume];
    ...
}

步驟六:AFURLSessionManagerTaskDelegat的方法處理回調數據

這個方法裏主要功能是處理接收到的數據,調用保存的任務完成的block,併發送接收完成的通知

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
    ...

    if(error){
        ...
    } else {
        ...
    }
}

步驟7:清理NSURLSessionDataTask配置

刪除NSURLSessionDataTask的配置信息,如:刪除監控收發數據進度的通知、任務開啓或掛起的通知、從mutableTaskDelegatesKeyedByTaskIdentifier中刪除任務的delegate等等。之後,回調用戶設置的block(成功或者失敗的回調),等block執行結束後,就結束這個GET請求。

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    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];
        // 清理dataTask的配置信息
        [self removeDelegateForTask:task];
    }

    if (self.taskDidComplete) {
        // 回調用戶的block
        self.taskDidComplete(session, task, error);
    }
}

// 清理dataTask配置
- (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];
}

下面介紹一下GETPOST在請求時,封裝URLHTTPBody的差別:

  • GET請求

parameters字典轉換爲key=value&key=value的形式,並附加在用戶設置的url的後面作爲請求的url

  • POST請求

parameters字典轉換爲key=value&key=value的形式,然後添加到HTTPBody裏面作爲請求體發送

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    // 添加用戶配置的header的key-value值
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    ...

    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        // GET 請求
        if (query && query.length > 0) {
            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
        // POST 請求
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

4.普通請求(直接用NSURLSession)

AFNetworking除了直接用GETPOST發送請求外,還可以直接用AFURLSessionManager發送網絡請求,而GETPOST內部其實也是調用的AFURLSessionManager,這樣我們也可以手動設置任務的開啓或掛起。

下面是AFNetworkingGithub的示例代碼,關於上傳或下載代碼,請參考AFNetworking的介紹

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

    NSURL *URL = [NSURL URLWithString:@"http://fishbay.cn"];
    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];

至此,AFNetworking的分析到此結束,本文只是從應用的角度分析了一下源碼的實現,如有不足之處,歡迎指正。(本文部分圖片來自互聯網,版權歸原作者所有)

參考資料

AFNetworking庫下載地址

AFNetworking3.0源碼解析1

AFNetworking3.0源碼解析2

iOS網絡框架-AFNetworking3.1.0源碼解讀

AFNetworking源碼閱讀系列

AFNetworking 3.0 源碼解讀(二)之 AFSecurityPolicy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章