AFNetworking
是iOS開發中最常用的第三方開源庫之一,它主要用於進行網絡請求。AFNetworking
主要是對HTTP協議和iOS網絡相關庫的封裝,讀一讀源碼可以加深對HTTP協議和iOS網絡編程的理解。
AFNetworking的結構
AFNetworking
主要分爲四個模塊:
- 處理請求和回覆的序列化模塊:Serialization
- 網絡安全模塊:Security
- 網絡監測模塊:Reachability
- 處理通訊的會話模塊:NSURLSession
其中NSURLSession是最常使用的模塊,也是綜合模塊,它引用了其他的幾個模塊,而其他幾個模塊都是獨立的模塊,所以對AFNetworking
的學習就先從這些單獨的模塊開始。
Serialization模塊
Serialization模塊包括請求序列化AFURLRequestSerialization
和響應序列化AFURLResponseSerialization
,它們主要功能:
AFURLRequestSerialization
用來將字典參數編碼成URL傳輸參數,並提供上傳文件的基本功能實現。AFURLResponseSerialization
用來處理服務器返回數據,提供返回碼校驗和數據校驗的功能。
Serialization分爲四個文件,分別來看這四個文件的內容。
AFURLRequestSerialization.h
先聲明瞭一個協議AFURLRequestSerialization
繼承了NSSecureCoding
和NSCopying
來保證所有實現這個序列化協議的序列化器類都有安全編碼和複製的能力。協議也定義了序列化的規範方法。(雖然目前只有一個類實現了協議,感覺沒啥必要..但這是一種規範,有利於擴展。)
/**
AFURLRequestSerialization協議可以被一個編碼特定http請求的對象實現。
請求序列化器(Request serializer)可以編碼查詢語句、HTTP請求體,如果必須的話,可以自行設置合適的HTTP請求體內容(如:Agent:iOS)。
例如,一個JSON請求序列化器會把請求體Content-Type設置爲application/json。
*/
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
/**
返回一個使用了指定參數編碼的請求的拷貝。
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
公開屬性
HTTP序列化器類AFHTTPRequestSerializer
,實現了AFURLRequestSerialization
協議,並參考了NSMutableURLRequest
類聲明瞭很多請求設置相關屬性。
AFHTTPRequestSerializer和其主要公開屬性
/**
AFHTTPRequestSerializer實現了AFURLRequestSerialization協議,爲查詢語句、URL表單編碼參數的序列化提供一個具體的實現和默認的請求頭,以及狀態碼和內容類型的校驗。
所有的request和response都被鼓勵去繼承AFHTTPRequestSerializer類,以確保默認方法和屬性的一致性。
*/
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
/**
字符串編碼方式,默認爲NSUTF8StringEncoding
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
緩存策略。默認爲NSURLRequestUseProtocolCachePolicy
參考NSMutableURLRequest -setCachePolicy:
*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
是否用cookie來處理創建的請求。默認爲YES
參考NSMutableURLRequest -setHTTPShouldHandleCookies
*/
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
創建的請求在收到上個傳輸(transmission)響應之前是否繼續發送數據。
默認爲NO(即等待上次傳輸完成後再請求)
參考NSMutableURLRequest -setHTTPShouldUsePipelining:
*/
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
/**
請求的網絡服務類型。
這個服務類型向整個網絡傳輸層次提供了一個關於該請求目的的提示。
(The service type is used to provide the networking layers a hint of the purpose of the request.)
默認爲NSURLNetworkServiceTypeDefault
參考NSMutableURLRequest -setNetworkServiceType:
*/
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
/**
請求的超時間隔,單位秒。默認爲60秒
參考NSMutableURLRequest -setTimeoutInterval:
*/
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
/**
序列請求的默認請求頭。默認值包括
'Accept-Language’ 內容爲 'NSLocale +preferredLanguages’ 方法獲取的語音
'User-Agent’ 內容爲各種bundle的標誌已經系統信息
可以使用'setValue:forHTTPHeaderField:’方法添加或刪除請求頭
*/
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
/**
哪些HTTP請求方法會將參數編碼成查詢字符串(如:name=xgb&gender=1)。默認爲GET, HEAD和DELETE。
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
/**
需要將參數轉換成查詢語句的HTTP請求方式。默認包括GET、HEAD和DELETE。
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
@end
屬性的理解:stringEncoding
一般服務器都是utf-8格式,沒必要用到其他。cachePolicy
緩存策略,蘋果主要定義了四種可供選擇的緩存策略:
/**
NSURLRequestUseProtocolCachePolicy是默認的緩存策略,它使用當前URL的協議中預置的緩存策略,無論這個協議是http,還是說你自己定義協議。
對於常見的http協議來說,這個策略根據請求的頭來執行緩存策略。服務器可以在返回的響應頭中加入Expires策略或者Cache-Control策略來告訴客戶端應該執行的緩存行爲,同時配合Last-Modified等頭來控制刷新的時機。
關於詳細的web緩存處理https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn
*/
NSURLRequestUseProtocolCachePolicy
/**
不使用緩存,相當於請求頭的no-store,在進行包含個人隱私數據或銀行業務數據等敏感信息的請求時可以使用。
*/
NSURLRequestReloadIgnoringCacheData
/**
NSURLRequestReturnCacheDataElseLoad 這個策略比較有趣,它會一直償試讀取緩存數據,直到無法沒有緩存數據的時候,纔會去請求網絡。
這個策略有一個重大的缺陷導致它根本無法被使用,即它根本沒有對緩存的刷新時機進行控制,如果你要去使用它,那麼需要額外的進行對緩存過期進行控制。
*/
NSURLRequestReturnCacheDataElseLoad
/**
只讀緩存並且即時緩存不存在都不去請求
*/
NSURLRequestReturnCacheDataDontLoad
HTTPShouldHandleCookies
、HTTPShouldUsePipelining
這兩個屬性一般不需要修改,使用默認即可。networkServiceType
用於設置這個請求的系統處理優先級,這個屬性會影響系統對網絡請求的喚醒速度,例如FaceTime使用了VoIP協議就需要設置爲NSURLNetworkServiceTypeVoIP來使得在後臺接收到數據時也能快速喚醒應用,一般情況下不需要用到。timeoutInterval
請求超時時間。HTTPRequestHeaders
其他請求頭設置。
API
AFHTTPRequestSerializer
的API分兩種,一種是普通的參數請求,另一種是需要上傳文件的請求,如下:
/** 通過URL字符串和字典參數來構建請求 */
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
/** 上傳文件的API,需要通過實現了AFMultipartFormData協議的formData對象處理待上傳文件 */
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
AFMultipartFormData協議
AFHTTPRequestSerializer
實現了對上傳文件的支持,AFMultipartFormData
協議就是定義添加需上傳文件的方法,有類似以下的方法:
/** 通過URL定位待上傳文件 */
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
/** 通過NSInputStream定義待上傳文件 */
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
/** 通過NSData數據上傳 */
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
AFURLRequestSerialization.m
工具方法
在瞭解AFURLRequestSerialization
怎麼實現之前,先來看下AFURLRequestSerialization.m
定義的一些工具方法:
AFPercentEscapedStringFromString
方法用於將字符串轉化成符合標準的URL編碼字符串,代碼如下:
NSString * AFPercentEscapedStringFromString(NSString *string) {
// does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@";
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
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