AFNetworking到底長啥樣(上)

AFNetworking是iOS界知名的網絡三方庫,現已完全取代了ASI。最新的AFNetworking3.0也早已從NSURLConnection切換到了NSURLSession,使用起來也更加方便。作爲一名不斷探索的資深iOSer,還是要看看源碼提升下內功。

一、概述

首先看下AFNetworking的結構及其繼承關係:

Class SuperClass Description
AFURLSessionManager NSObject ①用於管理NSURLSession實例。②負責生成dataTask、uploadTask和downloadTask。
AFHTTPSessionManager AFURLSessionManager AFURLSessionManager的子類,封裝了網絡請求並提供了Convenience Methods發起HTTP請求。
AFHTTPRequestSerializer NSObject 生成網絡請求所需的Request,包括對參數的處理。
AFHTTPResponseSerializer NSObject 解析返回來的Response,並驗證合法性。
AFSecurityPolicy NSObject 主要處理HTTPs通信。
AFURLSessionManagerTaskDelegate NSObject 作爲task的delegate,調用回調。

涉及的主要類都在上表中了,下面簡單說下其他的輔助類:

(1)_AFURLSessionTaskSwizzling:這個類只做一件事:使用Method Swizzling更改NSURLSessionDataTask及其父類的resumesuspend實現,在其調用時發送消息:AFNSURLSessionTaskDidResumeNotificationAFNSURLSessionTaskDidSuspendNotification即:

- (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_resume];

    if (state != NSURLSessionTaskStateRunning) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
    }
}

- (void)af_suspend {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_suspend];

    if (state != NSURLSessionTaskStateSuspended) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
    }
}

(2)AFJSONRequestSerializer:是AFHTTPRequestSerializer的子類,相比於AFHTTPRequestSerializer,它增加了對parameters是否是合法JSON格式的校驗。在POST情況下,parameters會通過NSJSONSerialization轉化爲NSData放到HTTPBody裏。此外,header的Content-Type也會被設置爲application/json

(3)AFQueryStringPair:包含fieldvalue屬性,用於表示參數(eg. name='layne'),並且field和value要經過“PercentEscaped”處理,處理函數如下:

NSString * AFPercentEscapedStringFromString(NSString *string) {
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";

    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

    // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
    // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

    static NSUInteger const batchSize = 50;

    NSUInteger index = 0;
    NSMutableString *escaped = @"".mutableCopy;

    while (index < string.length) {
        NSUInteger length = MIN(string.length - index, batchSize);
        NSRange range = NSMakeRange(index, length);

        // To avoid breaking up character sequences such as emoji
        range = [string rangeOfComposedCharacterSequencesForRange:range];

        NSString *substring = [string substringWithRange:range];
        NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
        [escaped appendString:encoded];

        index += range.length;
    }

    return escaped;
}

這裏有兩點需要說明:

①對於字符截斷問題(eg.emoji),這裏使用了:rangeOfComposedCharacterSequencesForRange:,根據給定的range調整實際的range來防止字符截斷。

②這裏設置了batchSize分塊進行escape。爲啥要這麼做?FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028給出了具體解釋:

Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead.

簡單說就是,在8.1和8.2上超過100箇中文字符會掛。

(4)AFStreamingMultipartFormData用於multipart方式上傳的formData.

(5)AFHTTPBodyPart

(6)AFMultipartBodyStream

(7)AFJSONResponseSerializerAFHTTPResponseSerializer的子類,解析JSON格式的response.

(8)AFXMLParserResponseSerializerAFHTTPResponseSerializer的子類,解析(NSXMLParser)XML格式的response.

(9)AFXMLDocumentResponseSerializerAFHTTPResponseSerializer的子類,解析(NSXMLDocument)XML格式的response.

(10)AFPropertyListResponseSerializerAFHTTPResponseSerializer的子類,解析(NSXMLDocument)PropertyList格式的response,

(11)AFImageResponseSerializerAFHTTPResponseSerializer的子類,解析圖片response。

(12)AFCompoundResponseSerializerAFHTTPResponseSerializer的子類,解析複合類型的response。

二、類分析

1.AFURLSessionManager

AFURLSessionManager是管理網絡請求的主類,它的結構如下:

  • 管理着

    一個session(NSURLSession實例),用於發起網絡請求。

    一個operationQueue,用於執行代理回調。

    一個responseSerializer(實現了AFURLResponseSerialization),用於response解析。

    一個securityPolicy(AFSecurityPolicy實例),用於HTTPs配置。

    一個reachabilityManager(AFNetworkReachabilityManager實例),用於網絡連通性監聽。

  • 通過重寫tasksdataTasksuploadTasksdownloadTasks屬性的getter方法,使用getTasksWithCompletionHandler:獲取session管理的tasks。

  • 提供多種生成task的函數。如:

    -dataTaskWithRequest:completionHandler:
    -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
    
    -uploadTaskWithRequest:fromFile:progress:completionHandler:
    -uploadTaskWithRequest:fromData:progress:completionHandler:
    -uploadTaskWithStreamedRequest:progress:completionHandler:
    
    -downloadTaskWithRequest:progress:destination:completionHandler:
    -downloadTaskWithResumeData:progress:destination:completionHandler:
  • 監控上傳/下載進度。

    -uploadProgressForTask:   
    -downloadProgressForTask:
  • 定義回調block屬性,每個block對應NSURLSession相關的delegate方法。

    @property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
    @property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
    @property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession AF_API_UNAVAILABLE(macos);
    @property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
    #if AF_CAN_INCLUDE_SESSION_TASK_METRICS
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidFinishCollectingMetricsBlock taskDidFinishCollectingMetrics;
    #endif
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
  • 聲明瞭常量。

    //通知
    AFNetworkingTaskDidResumeNotification
    AFNetworkingTaskDidCompleteNotification
    AFNetworkingTaskDidSuspendNotification
    
    AFURLSessionDidInvalidateNotification
    AFURLSessionDownloadTaskDidFailToMoveFileNotification
    
    //通知AFNetworkingTaskDidCompleteNotification中userInfo的key
    AFNetworkingTaskDidCompleteResponseDataKey
    AFNetworkingTaskDidCompleteSerializedResponseKey
    AFNetworkingTaskDidCompleteResponseSerializerKey
    AFNetworkingTaskDidCompleteAssetPathKey
    AFNetworkingTaskDidCompleteErrorKey
  • 在生成task時爲每個task生成對應的delegate(AFURLSessionManagerTaskDelegate實例),並使用{<taskID:delegate>}的形式保存在可變字典mutableTaskDelegatesKeyedByTaskIdentifier中。

  • 作爲NSURLSession的delegate,實現的delegate方法有:

    /* ----------NSURLSessionDelegate---------- */
    //執行sessionDidBecomeInvalid block併發通知
    - (void)URLSession:didBecomeInvalidWithError:
    //生成disposition(NSURLSessionAuthChallengeDisposition實例),並調用completionHandler
    - (void)URLSession:didReceiveChallenge:completionHandler:
    //執行didFinishEventsForBackgroundURLSession block
    - (void)URLSessionDidFinishEventsForBackgroundURLSession:
    
    /* ----------NSURLSessionTaskDelegate---------- */
    //執行taskWillPerformHTTPRedirectionBlock生成新的request,並調用completionHandler
    - (void)URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:
    //生成disposition(NSURLSessionAuthChallengeDisposition實例),並調用completionHandler
    - (void)URLSession:task:didReceiveChallenge:completionHandler:
    //生成inputStream(NSInputStream實例),並調用completionHandler
    - (void)URLSession:task:needNewBodyStream:
    //轉到task delegate中執行,並執行taskDidSendBodyData block
    - (void)URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
    //轉到task delegate中執行,並執行taskDidComplete block
    - (void)URLSession:task:didCompleteWithError:
    
    /* ----------NSURLSessionDataDelegate---------- */
    //執行dataTaskDidReceiveResponse block生成disposition,並調用completionHandler
    - (void)URLSession:dataTask:didReceiveResponse:completionHandler:
    //重新設置task delegate,並調用dataTaskDidBecomeDownloadTask block
    - (void)URLSession:dataTask:didBecomeDownloadTask:
    //轉到task delegate中執行,並調用dataTaskDidReceiveData block
    - (void)URLSession:dataTask:didReceiveData:
    //執行dataTaskWillCacheResponse block生成cacheResponse,並調用completionHandler
    - (void)URLSession:dataTask:willCacheResponse:completionHandler:
    
    /* ----------NSURLSessionDownloadDelegate---------- */
    //轉到task delegate中執行,並移動文件
    - (void)URLSession:downloadTask:didFinishDownloadingToURL:
    //轉到task delegate中執行,並執行downloadTaskDidWriteData block
    - (void)URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
    //轉到task delegate中執行,並執行downloadTaskDidResume block
    - (void)URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:

2.AFURLSessionManagerTaskDelegate

AFURLSessionManagerTaskDelegate這個類雖然後綴是·-Delegate,但它並不是一個協議,而是一個繼承自NSObject的類。它和AFURLSessionManager都定義在文件AFURLSessionManager.m中。它的實例作爲task的代理使用

  • 包含一個manager屬性,使用weak回指使用它的AFURLSessionManager實例。

  • 包含控制上傳和下載進度的屬性uploadProgressdownloadProgress(均爲NSProgress實例),通過KVO監測各自的fractionCompleted,從而在結束時調用downloadProgressBlockuploadProgressBlock

  • 實現的delegate包括:

    /* ----------NSURLSessionTaskDelegate---------- */
    //構造userInfo,並使用manager的responseSerializer解析response,最後調用self.completionHandler.
    - (void)URLSession:task:didCompleteWithError:
    //更新uploadProgress屬性
    - (void)URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
    
    /* ----------NSURLSessionDataTask---------- */
    //更新downloadProgress屬性,並用mutableData保存接收到的數據
    - (void)URLSession:dataTask:didReceiveData:
    
    /* ----------NSURLSessionDownloadTask-------- */
    //更新downloadProgress屬性
    - (void)URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
    //更新downloadProgress屬性
    - (void)URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
    //清空downloadFileURL(nil),並移動文件
    - (void)URLSession:downloadTask:didFinishDownloadingToURL:

注:AFURLSessionManagerTaskDelegate實例本身不持有task,它們之間的代理關係是以{<taskID:delegate>}的形式保存在可變字典mutableTaskDelegatesKeyedByTaskIdentifier中的。

3.AFHTTPSessionManager

AFHTTPSessionManagerAFURLSessionManager的子類,它針對HTTP請求封裝了更爲便利的方法。它的結構如下:

  • 主要包含requestSerializer(AFHTTPRequestSerializer實例)和responseSerializer(AFHTTPResponseSerializer實例),分別用於request的封裝及response的解析。

  • 提供三個實例初始化方法:

    + (instancetype)manager;
    - (instancetype)initWithBaseURL:(nullable NSURL *)url;
    - (instancetype)initWithBaseURL:(nullable NSURL *)url sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration;

    最終調用的都是第三個函數。

  • 封裝的Convenience Method如下:

    • GET
    - GET:parameters:headers:progress:success:failure:
    • POST
    - POST:parameters:headers:progress:success:failure:
    - POST:paramters:headers:constructingBodyWithBlock:progress:success:failure:
    • HEAD
    - HEAD:parameters:headers:success:failure:
    • PUT
    - PUT:parameters:headers:success:failure:
    • PATCH
    - PATCH:parameters:headers:success:failure:
    • DELETE
    - DELETE:paramaters:headers:success:failure:

    注:上面只列出了有效的方法,其他的都已經被標記爲DEPRECATED_ATTRIBUTE了。

  • 除了包含...constructingBodyWithBlock…的POST函數外,其餘的convenience methods都是通過以下函數生成對應的dataTask:

    - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:
                                                                            URLString:
                                                                          parameters:
                                                                   uploadProgress:
                                                                downloadProgress:
                                                                                 success:
                                                                                 failure:

    在上述函數中,requestSerializer會通過HTTPMethod、URLString和parameters生成request,然後會調用父類的:dataTaskWithRequest:uploadProgress:downloadProgress: completionHandler:生成dataTask並返回。返回的dataTask會被resume啓動。

4.AFHTTPRequestSerializer

AFHTTPRequestSerializer繼承自NSObject,用於封裝request。

  • 實現了協議AFURLRequestSerialization。這個協議只有一個函數:

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                 withParameters:(id)parameters
                                          error:(NSError *__autoreleasing *)error;

    用於將參數包含到原始request中形成新的request。

  • 使用數組mutableHTTPRequestHeaders保存要包含在request header中的數據。默認包含Accept-LanguageUser-Agent。其中,在設置User-Agent時,爲了保證ASCII編碼規則,作者使用了ICU文本變換。

    CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)

    ICU 庫提供了一整套強大的文本變換功能,可以實現不用語系之間的字符轉換,如漢字轉拼音。在上面的例子中,User-Agent字段會先被轉換爲Latin,接着變換爲Latin-ASCII,最後清除所有不是ASCII的字符。 其他的變換可參考 ICU 用戶手冊

  • 採用KVO機制監測相關屬性,若用戶設置了對應屬性,該屬性會被記錄下來,在生成request時加入。

    allowsCellularAccess
    cachePolicy
    HTTPShouldHandleCookies
    HTTPShouldUsePipelining
    networkServiceType
    timeoutInterval
    • 首先重寫+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key,禁止以上6個字段的KVO自動觸發。
    • 在以上6個字段的setter中使用willChangeValueForKeydidChangeValueForKey手動觸發KVO。
  • 生成request使用以下函數:

    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                   URLString:(NSString *)URLString
                                  parameters:(id)parameters
                                       error:(NSError *__autoreleasing *)error

    執行的操作包括:

    ① 根據URLString和method創建mutableRequest

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    ② 使用KVC將KVO監測的6個字段(用戶設置過的)包含進mutableRequest中。

    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
          if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
              [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
          }
    }

    ③ 調用AFURLRequestSerialization協議方法- requestBySerializingRequest: withParameters: error:。在這個協議方法內部執行:

    • 設置request的header
    • 將parameters格式化。默認的格式化形如name=layne$age=30&job=engineer
    • 根據請求的Method(GET、POST、HEAD等)不同將參數加到request的不同位置(URL or Body)。

5.AFJSONRequestSerializer

AFJSONRequestSerializerAFHTTPRequestSerializer的子類,使用NSJSONSerialization將參數編碼成JSON格式,並設置Content-Typeapplication/json。它重寫了AFURLRequestSerialization協議方法:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        return [super requestBySerializingRequest:request withParameters:parameters error:error];
    }//若爲GET/HEAD/DELETE方法,由於參數都拼接在URL中,因此無所謂json不json,直接調用父類的方法即可。

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];//更新header數據

    if (parameters) {
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        }//設置Content-Type字段爲“application/json”

        if (![NSJSONSerialization isValidJSONObject:parameters]) {
            if (error) {
                NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)};
                *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
            }
            return nil;
        }//非法的json格式(NSDictionary)數據

        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];//json序列化

        if (!jsonData) {
            return nil;
        }

        [mutableRequest setHTTPBody:jsonData];
    }

    return mutableRequest;
}

6.AFHTTPResponseSerializer

AFHTTPResonseSerializer繼承自NSObject,實現了AFURLResponseSerialization協議:

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error;

在協議方法中根據acceptableStatusCodesacceptableContentTypes判斷response合法性。

7.AFJSONResponseSerializer

AFJSONResponseSerializerAFHTTPResponseSerializer的子類。

  • 設置acceptableContentTypes指定合法的content-type:

    application/json
    text/json
    text/javascript
  • 重寫AFURLResponseSerialization協議方法:

    - (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
                      data:(nullable NSData *)data
                     error:(NSError * _Nullable __autoreleasing *)error;

    在內部:

    (1)根據acceptableStatusCodesacceptableContentTypes判斷response合法性;

    (2)使用NSJSONSerialization將data轉換爲NSDictionary

    (3)根據removesKeysWithNullValues的值決定是否將NSDictionary中NSNull的數據清除。

以上就是AFNetworking主要的類結構及其功能。下一篇博客我們會以一個簡單的POST請求來走一遍邏輯,看看AFN到底是如何工作的。

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