AFNetworking源码研读

最近又看了一遍AFNetworking的源码,跟两年前看又感觉不一样了,并且再次加深了我对HTTP和网络编程的理解。AFNetworking源码的解读可以分成以下几个部分以及比较好的研读顺序:
1、AFURLRequestSerialization 请求序列化
2、AFURLResponseSerialization 回复序列化
3、AFSecurityPolicy 安全策略
4、AFNetworkReachabilityManager 网络检测
5、AFURLSessionManager 处理通讯会话
6、AFHTTPSessionManager HTTP请求的会话

AFURLRequestSerialization

AFURLRequestSerialization是个protocol,并非是一个类,这个协议继承了NSSecureCoding和NSCopying来保证所有实现这个序列化协议的序列化类都有安全编码和复制的能力
这个协议主要是用作对http请求的字典参数编码成URL传输参数,查询语句进行拼接,请求头的设置,请求体的设置以及请求之前的一些准备工作。我们看这个协议下就只有一个方法:

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

它将一个NSURLRequest的实例对象以及字典作为参数传递进去,然后经过一系列处理后返回一个新的NSURLRequest对象,这个一系列处理到底做了哪些处理,后面会详细分析源码。
我们刚才说过AFURLRequestSerialization只是一个协议,真正进行序列化的还是得有具体的类去实现,下面介绍的这个类AFHTTPRequestSerializer遵守了AFURLRequestSerialization这个协议并且实现了序列化的方法。
我们先看AFHTTPRequestSerializer的属性:

@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
//字符串编码方式,默认为NSUTF8StringEncoding
@property (nonatomic, assign) NSStringEncoding stringEncoding;
//该属性指定是否允许使用蜂窝连接,默认是允许的
@property (nonatomic, assign) BOOL allowsCellularAccess;
/*缓存策略,使用缓存的目的是为了使用的应用程序能更快速的响应,默认是NSURLRequestUseProtocolCachePolicy。具体工作:如果一个NSCachedURLResponse对于请求并不存在,数据将会从源端获取。如果请求拥有一个缓存的响应,那么URL加载系统会检查这个响应来决定,如果它指定内容必须重新生效的话。假如内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化。假如内容没有变化,那么响应就从本地缓存返回数据。如果内容变化了,那么数据将从源端获取。其他的比较简单,读者可自行查看。*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
//是否使用默认的cookies处理方式  默认为YES
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
//创建的请求在收到上个传输响应之前是否继续发送数据。默认为NO(即等待上次传输完成后再请求)
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
//网络服务类型 默认是NSURLNetworkServiceTypeDefault。networkServiceType用于设置这个请求的系统处理优先级,这个属性会影响系统对网络请求的唤醒速度,例如FaceTime使用了VoIP协议就需要设置为NSURLNetworkServiceTypeVoIP来使得在后台接收到数据时也能快速唤醒应用,一般情况下不需要用到
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
//请求超时时间  默认是60秒
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
//HTTP header 请求头  可以使用'setValue:forHTTPHeaderField:’方法添加或删除请求头
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
//哪些请求的方式需要将字典参数转换成URL查询语句
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;

我们再看看AFHTTPRequestSerializer怎么实现协议中的方法:

- (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存在即执行queryStringSerialization这个block,这个block是自定义字典转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;
            }
        }
    }
    //如果当前的请求方法是需要将请求转换成url查询语句的方式则进行url拼接,否则执行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]转换为URLEncodedStringValue值是machine[]=iphone和machine[]=mac的两个AFQueryStringPair对象;pet:{a:cat, b:dog}转换之后的值是pet[a]=cat和pet[b]=dog。 AFQueryStringFromParameters方法再以'&'符号对它们进行拼接。比如:

字典参数 {name:mei, from:beijing, machine:[iphone, mac], pet:{a:cat, b:dog}}
转换为 name=mei&from=beijing&machine[]=iphone&machine[]=mac&pet[a]=cat&pet[b]=dog

我们已经大致了解了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。根据AFHTTPRequestSerializerObservedKeyPaths方法对一些必要的属性使用KVO进行了监听。在这些被监听的属性的setter里面手动地发送通知,例如:


...

- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
    [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
    _HTTPShouldHandleCookies = HTTPShouldHandleCookies;
    [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}

...
AFHTTPRequestSerializer子类

AFHTTPRequestSerializer的子类主要是对参数的格式进行扩展。
AFJSONRequestSerializer将参数转换成JSON格式,如下:

+ (instancetype)serializer {
    return [self serializerWithWritingOptions:(NSJSONWritingOptions)0];
}

if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        }

覆盖执行原方法,以Content-Type : application/json的方式生成request

AFPropertyListRequestSerializer将参数转换成plist格式,如下:


+ (instancetype)serializer {
    return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 writeOptions:0];
}

 if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
        }

覆盖执行原方法,以Content-Type : application/x-plist的方式生成request

AFURLResponseSerialization

同理,AFURLResponseSerialization也是一个protocol,AFHTTPResponseSerializer实现了AFURLResponseSerialization协议,因为服务器返回的HTTP报文一般都有明确的数据类型(Content-Type),所以对这些数据的处理具体都在各个子类中实现。

AFHTTPResponseSerializer的属性只有两个:


//可接受的状态码,如果返回的状态码不在这个集合里将会校验失败
@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这些子类的并没有实现校验方法,只是在各自初始化的时候给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 安全策略

苹果已经封装了HTTPS连接的建立、数据的加密解密功能,但并没有验证证书是否合法,无法避免中间人攻击。要做到真正安全通讯,需要我们手动去验证服务端返回的证书。AFNetwork中的AFSecurityPolicy模块主要是用来验证HTTPS请求时证书是否正确。 AFSecurityPolicy封装了证书验证的过程,让用户可以轻易使用,除了去系统信任CA机构列表验证,还支持SSL Pinning方式的验证。我们来看源码:

@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>
//验证证书的模式,后面会详细介绍
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
//根据验证模式来返回用于验证服务器的证书。
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
//是否允许不信任的证书(证书无效、证书时间过期)通过验证 ,默认为NO.
@property (nonatomic, assign) BOOL allowInvalidCertificates;
//是否验证域名证书的CN(common name)字段。默认值为YES。
@property (nonatomic, assign) BOOL validatesDomainName;

下面介绍一下验证证书的模式:
AFSSLPinningModeNone: 这个模式表示不做SSLpinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。

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,则需要判断此服务器证书是否是系统信任的证书
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        //  如果服务器证书不是系统信任证书,且不允许不信任的证书通过验证则返回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]) {
             //判断本地证书里有没有服务端下发的证书
                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;
}

校验证书都是通过调用系统库<Security/Security.h>的方法。AFNetworking所做的操作主要是根据设置的AFSecurityPolicy对象的属性进行证书验证。当SSLPinningMode不进行设置或设置为AFSSLPinningModeNone时,将不进行验证,设置为AFSSLPinningModeCertificate会使用证书进行验证,设置为AFSSLPinningModePublicKey将直接使用证书里面的公钥进行验证。可以通过设置pinnedCertificates属性来设置验证所用的证书,也可以通过+certificatesInBundle:方法加载单独放在一个Bundle里的证书,如果不设置,AFNetworking会使用NSBundle的+bundleForClass:方法将放在AFNetworking.framework里面的cer文件作为验证所用证书。

AFNetworkReachabilityManager

AFNetworkReachabilityManager 实际上只是一个对底层 SystemConfiguration 库中的 C 函数封装的类,它为我们隐藏了 C 语言的实现,提供了统一的 Objective-C 语言接口.苹果的文档中也有一个类似的项目 Reachability 这里对网络状态的监控跟苹果官方的实现几乎是完全相同的。同样在 github 上有一个类似的项目叫做 Reachability ,不过这个项目由于命名的原因可能会在审核时被拒绝。无论是 AFNetworkReachabilityManager,苹果官方的项目或者说 github 上的 Reachability,它们的实现都是类似的,而在这里我们会以 AFNetworking 中的 AFNetworkReachabilityManager 为例来说明在 iOS 开发中,我们是怎样监控网络状态的。

//开始监听
- (void)startMonitoring {
    //关闭上一个监听任务
    [self stopMonitoring];

    if (!self.networkReachability) {
        return;
    }
    //每次回调被调用时,重新设置 networkReachabilityStatus 属性;调用 networkReachabilityStatusBlock
    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }

    };
    //一个结构体,用来存放上个创建的block对象以及两个block,分别对info的Block_copy和Block_release调用
    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    //当目标的网络状态改变时,会调用传入的回调
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    //在 主线程的Runloop 中开始监控网络状态
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    //获取当前的网络状态
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

大致流程是这样的开始监听之前首先关闭上一个监听的任务,调用了 SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);方法使得当前的self.networkReachability任务取消在main runloop中的监听。创建一个block,这个block在每次网络状态发送变化时都能调用,block重新设置了networkReachabilityStatus的属性,并且调用了networkReachabilityStatusBlock,用户在这个block里接收这个status.同时SCNetworkReachabilitySetCallback也设置了一个AFNetworkReachabilityCallback,这个回调里面调用了AFPostReachabilityStatusChange,我们再看源码:

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
    //在主线程中发送一个名为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];
    });
}

所以用户也可以使用接收通知的方式感知网络状态的变化。

AFURLSessionManager

AFNetworking通过AFURLSessionManager来对NSURLSession进行封装管理。AFURLSessionManager简化了用户网络请求的操作,使得用户只需以block的方式来更简便地进行网络请求操作,而无需实现类似NSURLSessionDelegate、NSURLSessionTaskDelegate等协议。用户只需使用到NSURLSessionTask的-resume、-cancel和-suspned等操作,以及在block中定义你需要的操作就可以。AFURLSessionManager可以说是AFNetworking的核心内容,主要做了以下的动作:
1、创建和管理 NSURLSessionTask
2、实现 NSURLSessionDelegate 等协议中的代理方法
3、使用 _AFURLSessionTaskSwizzling 方法
4、引入 AFSecurityPolicy 保证请求的安全
5、引入 AFNetworkReachabilityManager 监控网络状态

1、创建和管理 NSURLSessionTask
  • (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 是创建AFURLSessionManager的方法。其中对responseSerializer(响应序列化)、securityPolicy(安全设置)、reachabilityManager(网络监听)、mutableTaskDelegatesKeyedByTaskIdentifier(网络请求任务的保存字典)等参数进行了默认设置或初始化。
    这里解释一下有个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。其实理论上在初始化中调用这个方法应该一个task都不会有。通过断点调试发现确实如此,里面的数组都是空的。但是为啥要在这里写这个函数呢,后来发现,原来这是为了防止后台回来,重新初始化这个session,一些之前的后台请求任务,导致程序的crash。

NSURLSessionDataTask的创建有好几个方法,代码都差不多,我们看其中一个[XX dataTaskWithRequest:completionHandler:]方法的源码实现:

//传入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];
}
//接着执行下面的函数
- (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(^{
        //创建生成NSURLSessionDataTask对象
        dataTask = [self.session dataTaskWithRequest:request];
    });
    //执行
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}
//接着执行下面的函数
- (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;
    //马上到重点了,我们点进去看如何实现
    [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 时,不会出现线程竞争(race)的问题。

2、实现 NSURLSessionDelegate 等协议中的代理方法

因为在创建AFURLSessionManager的时候,我们把NSURLSessionDelegate设置给了self,所以我们实现了NSURLSessionDelegate的代理方法。这个方法一般在Session invallid的时候,也是就调用了invalidateAndCancel 和finishTasksAndInvalidate方法的时候,才会被调用。

- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    //通过setSessionDidBecomeInvalidBlock:方法设置sessionDidBecomeInvalid
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

后面还有好几个协议方法就省略了
3、使用 _AFURLSessionTaskSwizzling 方法

为了在调用resume和suspend的时候可以发通知,作者使用了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进行了封装,丰富了用户调用的API,如GET,HEAD,POST,PUT,PATCH,DELETE等不同的调用方式。最终还都是使用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源码的理解,如有笔误,欢迎指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351