最近又看了一遍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源码的理解,如有笔误,欢迎指正。
网友评论