AFNetworking源碼研讀

最近又看了一遍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源碼的理解瑟押,如有筆誤搀捷,歡迎指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末多望,一起剝皮案震驚了整個濱河市嫩舟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌怀偷,老刑警劉巖家厌,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異枢纠,居然都是意外死亡像街,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門晋渺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镰绎,“玉大人,你說我怎么就攤上這事木西〕肫埽” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵八千,是天一觀的道長吗讶。 經(jīng)常有香客問我,道長恋捆,這世上最難降的妖魔是什么照皆? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮沸停,結(jié)果婚禮上膜毁,老公的妹妹穿的比我還像新娘。我一直安慰自己愤钾,他們只是感情好瘟滨,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著能颁,像睡著了一般杂瘸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上伙菊,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天败玉,我揣著相機與錄音敌土,去河邊找鬼。 笑死绒怨,一個胖子當著我的面吹牛纯赎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播南蹂,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼犬金,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了六剥?” 一聲冷哼從身側(cè)響起晚顷,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疗疟,沒想到半個月后该默,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡策彤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年栓袖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片店诗。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡裹刮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庞瘸,到底是詐尸還是另有隱情捧弃,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布擦囊,位于F島的核電站违霞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瞬场。R本人自食惡果不足惜买鸽,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贯被。 院中可真熱鬧眼五,春花似錦、人聲如沸刃榨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枢希。三九已至,卻和暖如春朱沃,著一層夾襖步出監(jiān)牢的瞬間苞轿,已是汗流浹背茅诱。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搬卒,地道東北人瑟俭。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像契邀,于是被迫代替她去往敵國和親摆寄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345