最近又看了一遍AFNetworking的源碼,跟兩年前看又感覺不一樣了蜂嗽,并且再次加深了我對HTTP和網(wǎng)絡編程的理解墨叛。AFNetworking源碼的解讀可以分成以下幾個部分以及比較好的研讀順序:
1闷畸、AFURLRequestSerialization 請求序列化
2尝盼、AFURLResponseSerialization 回復序列化
3、AFSecurityPolicy 安全策略
4佑菩、AFNetworkReachabilityManager 網(wǎng)絡檢測
5盾沫、AFURLSessionManager 處理通訊會話
6、AFHTTPSessionManager HTTP請求的會話
AFURLRequestSerialization
AFURLRequestSerialization是個protocol殿漠,并非是一個類疮跑,這個協(xié)議繼承了NSSecureCoding和NSCopying來保證所有實現(xiàn)這個序列化協(xié)議的序列化類都有安全編碼和復制的能力
這個協(xié)議主要是用作對http請求的字典參數(shù)編碼成URL傳輸參數(shù),查詢語句進行拼接凸舵,請求頭的設置,請求體的設置以及請求之前的一些準備工作失尖。我們看這個協(xié)議下就只有一個方法:
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
它將一個NSURLRequest的實例對象以及字典作為參數(shù)傳遞進去啊奄,然后經(jīng)過一系列處理后返回一個新的NSURLRequest對象,這個一系列處理到底做了哪些處理掀潮,后面會詳細分析源碼菇夸。
我們剛才說過AFURLRequestSerialization只是一個協(xié)議,真正進行序列化的還是得有具體的類去實現(xiàn)仪吧,下面介紹的這個類AFHTTPRequestSerializer遵守了AFURLRequestSerialization這個協(xié)議并且實現(xiàn)了序列化的方法庄新。
我們先看AFHTTPRequestSerializer的屬性:
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
//字符串編碼方式,默認為NSUTF8StringEncoding
@property (nonatomic, assign) NSStringEncoding stringEncoding;
//該屬性指定是否允許使用蜂窩連接薯鼠,默認是允許的
@property (nonatomic, assign) BOOL allowsCellularAccess;
/*緩存策略择诈,使用緩存的目的是為了使用的應用程序能更快速的響應,默認是NSURLRequestUseProtocolCachePolicy出皇。具體工作:如果一個NSCachedURLResponse對于請求并不存在羞芍,數(shù)據(jù)將會從源端獲取。如果請求擁有一個緩存的響應郊艘,那么URL加載系統(tǒng)會檢查這個響應來決定荷科,如果它指定內(nèi)容必須重新生效的話。假如內(nèi)容必須重新生效纱注,將建立一個連向源端的連接來查看內(nèi)容是否發(fā)生變化畏浆。假如內(nèi)容沒有變化,那么響應就從本地緩存返回數(shù)據(jù)狞贱。如果內(nèi)容變化了刻获,那么數(shù)據(jù)將從源端獲取。其他的比較簡單斥滤,讀者可自行查看将鸵。*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
//是否使用默認的cookies處理方式 默認為YES
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
//創(chuàng)建的請求在收到上個傳輸響應之前是否繼續(xù)發(fā)送數(shù)據(jù)勉盅。默認為NO(即等待上次傳輸完成后再請求)
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
//網(wǎng)絡服務類型 默認是NSURLNetworkServiceTypeDefault。networkServiceType用于設置這個請求的系統(tǒng)處理優(yōu)先級顶掉,這個屬性會影響系統(tǒng)對網(wǎng)絡請求的喚醒速度草娜,例如FaceTime使用了VoIP協(xié)議就需要設置為NSURLNetworkServiceTypeVoIP來使得在后臺接收到數(shù)據(jù)時也能快速喚醒應用,一般情況下不需要用到
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
//請求超時時間 默認是60秒
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
//HTTP header 請求頭 可以使用'setValue:forHTTPHeaderField:’方法添加或刪除請求頭
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
//哪些請求的方式需要將字典參數(shù)轉(zhuǎn)換成URL查詢語句
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
我們再看看AFHTTPRequestSerializer怎么實現(xiàn)協(xié)議中的方法:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
//copy一份新的request
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//給它設置請求頭
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) {
NSError *serializationError;
//如果queryStringSerialization存在即執(zhí)行queryStringSerialization這個block痒筒,這個block是自定義字典轉(zhuǎn)url的序列化方式宰闰,可以通過setQueryStringSerializationWithBlock:這個方法設置
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
//如果沒有自定義序列化方式,就使用默認的序列化方式
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//如果當前的請求方法是需要將請求轉(zhuǎn)換成url查詢語句的方式則進行url拼接簿透,否則執(zhí)行else設置到body里面
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
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
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;
}
下面我們來看看默認的序列化方式
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
//字典處理后生成一組組的查詢單元移袍,遍歷改查詢單元
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//對查詢單元進行拼接
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
其中最主要的方法是AFQueryStringPairsFromKeyAndValue,它將字典的每一個鍵值對生成對應的AFQueryStringPair對象老充,例如將machine:[iphone, mac]轉(zhuǎn)換為URLEncodedStringValue值是machine[]=iphone和machine[]=mac的兩個AFQueryStringPair對象;pet:{a:cat, b:dog}轉(zhuǎn)換之后的值是pet[a]=cat和pet[b]=dog葡盗。 AFQueryStringFromParameters方法再以'&'符號對它們進行拼接。比如:
字典參數(shù) {name:mei, from:beijing, machine:[iphone, mac], pet:{a:cat, b:dog}}
轉(zhuǎn)換為 name=mei&from=beijing&machine[]=iphone&machine[]=mac&pet[a]=cat&pet[b]=dog
我們已經(jīng)大致了解了AFHTTPRequestSerializer序列化的過程啡浊,我們再來看一下它的初始化:
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f);
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
NSString *userAgent = nil;
#if TARGET_OS_IOS
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
初始化的過程主要是設置HTTP的頭部信息觅够,包括Accept-Language、User-Agent巷嚣。根據(jù)AFHTTPRequestSerializerObservedKeyPaths方法對一些必要的屬性使用KVO進行了監(jiān)聽喘先。在這些被監(jiān)聽的屬性的setter里面手動地發(fā)送通知,例如:
...
- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
[self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
_HTTPShouldHandleCookies = HTTPShouldHandleCookies;
[self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}
...
AFHTTPRequestSerializer子類
AFHTTPRequestSerializer的子類主要是對參數(shù)的格式進行擴展廷粒。
AFJSONRequestSerializer將參數(shù)轉(zhuǎn)換成JSON格式窘拯,如下:
+ (instancetype)serializer {
return [self serializerWithWritingOptions:(NSJSONWritingOptions)0];
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
覆蓋執(zhí)行原方法,以Content-Type : application/json的方式生成request
AFPropertyListRequestSerializer將參數(shù)轉(zhuǎn)換成plist格式坝茎,如下:
+ (instancetype)serializer {
return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 writeOptions:0];
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
}
覆蓋執(zhí)行原方法涤姊,以Content-Type : application/x-plist的方式生成request
AFURLResponseSerialization
同理旺拉,AFURLResponseSerialization也是一個protocol,AFHTTPResponseSerializer實現(xiàn)了AFURLResponseSerialization協(xié)議频轿,因為服務器返回的HTTP報文一般都有明確的數(shù)據(jù)類型(Content-Type)陨舱,所以對這些數(shù)據(jù)的處理具體都在各個子類中實現(xiàn)特姐。
AFHTTPResponseSerializer的屬性只有兩個:
//可接受的狀態(tài)碼嘹锁,如果返回的狀態(tài)碼不在這個集合里將會校驗失敗
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
//可接受的MIME類型呆盖,如果返回的類型不在這個集合里將會校驗失敗
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
//用來校驗acceptableStatusCodes和acceptableContentTypes
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
AFURLResponseSerialization的子類
AFJSONResponseSerializer呢灶、AFXMLParserResponseSerializer曾雕、AFXMLDocumentResponseSerializer和措、AFPropertyListResponseSerializer庄呈、AFImageResponseSerializer、AFCompoundResponseSerializer這些子類的并沒有實現(xiàn)校驗方法派阱,只是在各自初始化的時候給acceptableContentTypes賦值了诬留。
比如AFJSONResponseSerializer源碼:
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
AFXMLParserResponseSerializer:
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
AFXMLDocumentResponseSerializer:
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
AFPropertyListResponseSerializer:
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil];
......
AFSecurityPolicy 安全策略
蘋果已經(jīng)封裝了HTTPS連接的建立、數(shù)據(jù)的加密解密功能,但并沒有驗證證書是否合法文兑,無法避免中間人攻擊盒刚。要做到真正安全通訊,需要我們手動去驗證服務端返回的證書绿贞。AFNetwork中的AFSecurityPolicy模塊主要是用來驗證HTTPS請求時證書是否正確因块。 AFSecurityPolicy封裝了證書驗證的過程,讓用戶可以輕易使用籍铁,除了去系統(tǒng)信任CA機構(gòu)列表驗證涡上,還支持SSL Pinning方式的驗證。我們來看源碼:
@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>
//驗證證書的模式,后面會詳細介紹
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
//根據(jù)驗證模式來返回用于驗證服務器的證書拒名。
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
//是否允許不信任的證書(證書無效吩愧、證書時間過期)通過驗證 ,默認為NO.
@property (nonatomic, assign) BOOL allowInvalidCertificates;
//是否驗證域名證書的CN(common name)字段增显。默認值為YES雁佳。
@property (nonatomic, assign) BOOL validatesDomainName;
下面介紹一下驗證證書的模式:
AFSSLPinningModeNone: 這個模式表示不做SSLpinning,只跟瀏覽器一樣在系統(tǒng)的信任機構(gòu)列表里驗證服務端返回的證書同云。若證書是信任機構(gòu)簽發(fā)的就會通過甘穿,若是自己服務器生成的證書,這里是不會通過的梢杭。
AFSSLPinningModeCertificate:這個模式表示用證書綁定方式驗證證書,需要客戶端保存有服務端的證書拷貝秸滴,這里驗證分兩步武契,第一步驗證證書的域名/有效期等信息,第二步是對比服務端返回的證書跟客戶端返回的是否一致荡含。
AFSSLPinningModePublicKey:這個模式同樣是用證書綁定方式驗證咒唆,客戶端要有服務端的證書拷貝,只是驗證時只驗證證書里的公鑰释液,不驗證證書的有效期等信息全释。
我們來看默認的驗證證書的模式:
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}
默認的AFSSLPinningModeNone模式:即不進行證書驗證
下面的代碼就是用來判斷HTTPS請求的證書驗證是否通過。也是AFSecurityPolicy的核心代碼
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//當使用自建證書時误债,必須使用AFSSLPinningModeCertificate和AFSSLPinningModePublicKey這兩種模式
//沒有證書浸船,也沒必要校驗
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//當SSLPinningMode等于AFSSLPinningModeNone時,如果allowInvalidCertificates為YES時寝蹈,則代表服務器任何證書都能驗證通過李命;如果它為NO,則需要判斷此服務器證書是否是系統(tǒng)信任的證書
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
// 如果服務器證書不是系統(tǒng)信任證書箫老,且不允許不信任的證書通過驗證則返回NO
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
//判斷本地證書里有沒有服務端下發(fā)的證書
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
//判斷本地證書里是否有服務端的證書公鑰
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
校驗證書都是通過調(diào)用系統(tǒng)庫<Security/Security.h>的方法封字。AFNetworking所做的操作主要是根據(jù)設置的AFSecurityPolicy對象的屬性進行證書驗證。當SSLPinningMode不進行設置或設置為AFSSLPinningModeNone時,將不進行驗證阔籽,設置為AFSSLPinningModeCertificate會使用證書進行驗證流妻,設置為AFSSLPinningModePublicKey將直接使用證書里面的公鑰進行驗證“手疲可以通過設置pinnedCertificates屬性來設置驗證所用的證書绅这,也可以通過+certificatesInBundle:方法加載單獨放在一個Bundle里的證書,如果不設置项贺,AFNetworking會使用NSBundle的+bundleForClass:方法將放在AFNetworking.framework里面的cer文件作為驗證所用證書君躺。
AFNetworkReachabilityManager
AFNetworkReachabilityManager 實際上只是一個對底層 SystemConfiguration 庫中的 C 函數(shù)封裝的類,它為我們隱藏了 C 語言的實現(xiàn)开缎,提供了統(tǒng)一的 Objective-C 語言接口.蘋果的文檔中也有一個類似的項目 Reachability 這里對網(wǎng)絡狀態(tài)的監(jiān)控跟蘋果官方的實現(xiàn)幾乎是完全相同的棕叫。同樣在 github 上有一個類似的項目叫做 Reachability ,不過這個項目由于命名的原因可能會在審核時被拒絕。無論是 AFNetworkReachabilityManager奕删,蘋果官方的項目或者說 github 上的 Reachability俺泣,它們的實現(xiàn)都是類似的,而在這里我們會以 AFNetworking 中的 AFNetworkReachabilityManager 為例來說明在 iOS 開發(fā)中完残,我們是怎樣監(jiān)控網(wǎng)絡狀態(tài)的伏钠。
//開始監(jiān)聽
- (void)startMonitoring {
//關閉上一個監(jiān)聽任務
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
//每次回調(diào)被調(diào)用時,重新設置 networkReachabilityStatus 屬性谨设;調(diào)用 networkReachabilityStatusBlock
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
//一個結(jié)構(gòu)體熟掂,用來存放上個創(chuàng)建的block對象以及兩個block,分別對info的Block_copy和Block_release調(diào)用
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
//當目標的網(wǎng)絡狀態(tài)改變時,會調(diào)用傳入的回調(diào)
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
//在 主線程的Runloop 中開始監(jiān)控網(wǎng)絡狀態(tài)
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
//獲取當前的網(wǎng)絡狀態(tài)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}
大致流程是這樣的開始監(jiān)聽之前首先關閉上一個監(jiān)聽的任務扎拣,調(diào)用了 SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);方法使得當前的self.networkReachability任務取消在main runloop中的監(jiān)聽赴肚。創(chuàng)建一個block,這個block在每次網(wǎng)絡狀態(tài)發(fā)送變化時都能調(diào)用二蓝,block重新設置了networkReachabilityStatus的屬性誉券,并且調(diào)用了networkReachabilityStatusBlock,用戶在這個block里接收這個status.同時SCNetworkReachabilitySetCallback也設置了一個AFNetworkReachabilityCallback,這個回調(diào)里面調(diào)用了AFPostReachabilityStatusChange刊愚,我們再看源碼:
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
//在主線程中發(fā)送一個名為AFNetworkingReachabilityDidChangeNotification的通知
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
});
}
所以用戶也可以使用接收通知的方式感知網(wǎng)絡狀態(tài)的變化踊跟。
AFURLSessionManager
AFNetworking通過AFURLSessionManager來對NSURLSession進行封裝管理。AFURLSessionManager簡化了用戶網(wǎng)絡請求的操作鸥诽,使得用戶只需以block的方式來更簡便地進行網(wǎng)絡請求操作商玫,而無需實現(xiàn)類似NSURLSessionDelegate、NSURLSessionTaskDelegate等協(xié)議牡借。用戶只需使用到NSURLSessionTask的-resume决帖、-cancel和-suspned等操作,以及在block中定義你需要的操作就可以蓖捶。AFURLSessionManager可以說是AFNetworking的核心內(nèi)容地回,主要做了以下的動作:
1、創(chuàng)建和管理 NSURLSessionTask
2、實現(xiàn) NSURLSessionDelegate 等協(xié)議中的代理方法
3刻像、使用 _AFURLSessionTaskSwizzling 方法
4畅买、引入 AFSecurityPolicy 保證請求的安全
5、引入 AFNetworkReachabilityManager 監(jiān)控網(wǎng)絡狀態(tài)
1细睡、創(chuàng)建和管理 NSURLSessionTask
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 是創(chuàng)建AFURLSessionManager的方法谷羞。其中對responseSerializer(響應序列化)、securityPolicy(安全設置)溜徙、reachabilityManager(網(wǎng)絡監(jiān)聽)湃缎、mutableTaskDelegatesKeyedByTaskIdentifier(網(wǎng)絡請求任務的保存字典)等參數(shù)進行了默認設置或初始化。
這里解釋一下有個block的蠢壹,如下:
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
這個方法是用來異步的獲取當前session的所有未完成的task嗓违。其實理論上在初始化中調(diào)用這個方法應該一個task都不會有。通過斷點調(diào)試發(fā)現(xiàn)確實如此图贸,里面的數(shù)組都是空的蹂季。但是為啥要在這里寫這個函數(shù)呢,后來發(fā)現(xiàn)疏日,原來這是為了防止后臺回來偿洁,重新初始化這個session,一些之前的后臺請求任務沟优,導致程序的crash涕滋。
NSURLSessionDataTask的創(chuàng)建有好幾個方法,代碼都差不多挠阁,我們看其中一個[XX dataTaskWithRequest:completionHandler:]方法的源碼實現(xiàn):
//傳入NSURLRequest對象和一個block何吝,返回一個NSURLSessionDataTask對象
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
}
//接著執(zhí)行下面的函數(shù)
- (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;
url_session_manager_create_task_safely(^{
//創(chuàng)建生成NSURLSessionDataTask對象
dataTask = [self.session dataTaskWithRequest:request];
});
//執(zhí)行
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
//接著執(zhí)行下面的函數(shù)
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
//馬上到重點了,我們點進去看如何實現(xiàn)
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
//存在了字典里面
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
原來是通過字典 mutableTaskDelegatesKeyedByTaskIdentifier 來存儲并管理每一個 NSURLSessionTask鹃唯,它以 taskIdentifier 為鍵存儲 task。因為NSMutableDictionary對象會將其key值進行copy和對其value值進行強引用瓣喊,所以無需再持有task和delegate坡慌。另外,該方法使用 NSLock 來保證不同線程使用 mutableTaskDelegatesKeyedByTaskIdentifier 時藻三,不會出現(xiàn)線程競爭(race)的問題洪橘。
2、實現(xiàn) NSURLSessionDelegate 等協(xié)議中的代理方法
因為在創(chuàng)建AFURLSessionManager的時候棵帽,我們把NSURLSessionDelegate設置給了self,所以我們實現(xiàn)了NSURLSessionDelegate的代理方法熄求。這個方法一般在Session invallid的時候,也是就調(diào)用了invalidateAndCancel 和finishTasksAndInvalidate方法的時候逗概,才會被調(diào)用弟晚。
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
//通過setSessionDidBecomeInvalidBlock:方法設置sessionDidBecomeInvalid
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
后面還有好幾個協(xié)議方法就省略了
3、使用 _AFURLSessionTaskSwizzling 方法
為了在調(diào)用resume和suspend的時候可以發(fā)通知,作者使用了swizzle方法替換了原方法的IMP卿城,源碼如下:
+ (void)load {
if (NSClassFromString(@"NSURLSessionTask")) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
- (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];
}
}
AFSecurityPolicy和AFNetworkReachabilityManager前面講過了
AFHTTPSessionManager
AFHTTPSessionManager繼承自AFURLSessionManager,在對NSURLSessionDataTask進行了封裝枚钓,豐富了用戶調(diào)用的API,如GET,HEAD,POST,PUT,PATCH,DELETE等不同的調(diào)用方式。最終還都是使用NSURLSessonDataTask里的dataTaskWithRequest:方法
- (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;
}
以上就是本人對AFNetworking源碼的理解瑟押,如有筆誤搀捷,歡迎指正。