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及其父類的resume
及suspend
實現,在其調用時發送消息:AFNSURLSessionTaskDidResumeNotification
和AFNSURLSessionTaskDidSuspendNotification
即:
- (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
:包含field
和value
屬性,用於表示參數(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)AFJSONResponseSerializer
:AFHTTPResponseSerializer
的子類,解析JSON格式的response.
(8)AFXMLParserResponseSerializer
:AFHTTPResponseSerializer
的子類,解析(NSXMLParser
)XML格式的response.
(9)AFXMLDocumentResponseSerializer
:AFHTTPResponseSerializer
的子類,解析(NSXMLDocument
)XML格式的response.
(10)AFPropertyListResponseSerializer
:AFHTTPResponseSerializer
的子類,解析(NSXMLDocument
)PropertyList格式的response,
(11)AFImageResponseSerializer
:AFHTTPResponseSerializer
的子類,解析圖片response。
(12)AFCompoundResponseSerializer
:AFHTTPResponseSerializer
的子類,解析複合類型的response。
二、類分析
1.AFURLSessionManager
AFURLSessionManager
是管理網絡請求的主類,它的結構如下:
-
管理着
一個
session
(NSURLSession實例),用於發起網絡請求。一個
operationQueue
,用於執行代理回調。一個
responseSerializer
(實現了AFURLResponseSerialization
),用於response解析。一個
securityPolicy
(AFSecurityPolicy
實例),用於HTTPs配置。一個
reachabilityManager
(AFNetworkReachabilityManager
實例),用於網絡連通性監聽。 -
通過重寫
tasks
、dataTasks
、uploadTasks
和downloadTasks
屬性的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
實例。 -
包含控制上傳和下載進度的屬性
uploadProgress
和downloadProgress
(均爲NSProgress
實例),通過KVO監測各自的fractionCompleted
,從而在結束時調用downloadProgressBlock
和uploadProgressBlock
。 -
實現的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
AFHTTPSessionManager
是AFURLSessionManager
的子類,它針對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-Language
和User-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中使用
willChangeValueForKey
和didChangeValueForKey
手動觸發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
AFJSONRequestSerializer
是AFHTTPRequestSerializer
的子類,使用NSJSONSerialization
將參數編碼成JSON格式,並設置Content-Type
爲application/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;
在協議方法中根據acceptableStatusCodes
和acceptableContentTypes
判斷response合法性。
7.AFJSONResponseSerializer
AFJSONResponseSerializer
是AFHTTPResponseSerializer
的子類。
-
設置
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)根據
acceptableStatusCodes
和acceptableContentTypes
判斷response合法性;(2)使用
NSJSONSerialization
將data轉換爲NSDictionary(3)根據
removesKeysWithNullValues
的值決定是否將NSDictionary中NSNull的數據清除。
以上就是AFNetworking主要的類結構及其功能。下一篇博客我們會以一個簡單的POST請求來走一遍邏輯,看看AFN到底是如何工作的。