【原】AFNetworking源码阅读(六)

本文转载请注明出处 —— polobymulberry-博客园

1. 前言


这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AFNetworking的网络安全策略,尤其指HTTPS(大家可以先简单了解下HTTPS)。再一个就是分析下AFNetworkReachabilityManager文件,看看AFNetworking如何解决网络状态的检测。

2. AFSecurityPolicy - 网络安全策略


之前我们在AFURLSessionManager中实现了NSURLSessionDelegate的代理方法- (void)URLSession:didReceiveChallenge:completionHandler:,其中我们提到过,如果iOS客户端需要向服务器发送一个凭证(Credential)来确认认证挑战(authentication challenge),那么先得使用AFNetworking的安全策略类-AFSecurityPolicy来检查服务器端是否可以信任(在authenticationMethod属性为NSURLAuthenticationMethodServerTrust情况下),而检查的方式就是通过- [AFSecurityPolicy evaluateServerTrust:forDomain:]这个函数。

2.1 - [AFSecurityPolicy evaluateServerTrust:forDomain:]

// 根据severTrust和domain来检查服务器端发来的证书是否可信
// 其中SecTrustRef是一个CoreFoundation类型,用于对服务器端传来的X.509证书评估的
// 而我们都知道,数字证书的签发机构CA,在接收到申请者的资料后进行核对并确定信息的真实有效,然后就会制作一份符合X.509标准的文件。证书中的证书内容包含的持有者信息和公钥等都是由申请者提供的,而数字签名则是CA机构对证书内容进行hash加密后得到的,而这个数字签名就是我们验证证书是否是有可信CA签发的数据。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(nullable NSString *)domain;

具体函数我们直接看函数源码:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
/**
self.allowInvalidCertificates==YES表示如果此处允许使用自建证书(服务器自己弄的CA证书,非官方),并且还想验证domain是否有效(self.validatesDomainName == YES),也就是说你想验证自建证书的domain是否有效。那么你必须使用pinnedCertificates(就是在客户端保存服务器端颁发的证书拷贝)才可以。但是你的SSLPinningMode为AFSSLPinningModeNone,表示你不使用SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。所以当然你的客户端上没有你导入的pinnedCertificates,同样表示你无法验证该自建证书。所以都返回NO。最终结论就是要使用服务器端自建证书,那么就得将对应的证书拷贝到iOS客户端,并使用AFSSLPinningMode或AFSSLPinningModePublicKey
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == )) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
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) {
// 如果需要验证domain,那么就使用SecPolicyCreateSSL函数创建验证策略,其中第一个参数为true表示验证整个SSL证书链,第二个参数传入domain,用于判断整个证书链上叶子节点表示的那个domain是否和此处传入domain一致
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 如果不需要验证domain,就使用默认的BasicX509验证策略
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 为serverTrust设置验证策略,即告诉客户端如何验证serverTrust
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// 如果SSLPinningMode为 AFSSLPinningModeNone,表示你不使用SSL pinning,但是我允许自建证书,那么返回YES,或者使用AFServerTrustIsValid函数看看serverTrust是否可信任,如果信任,也返回YES
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
// 既不允许自建证书,而且使用AFServerTrustIsValid函数又返回NO,那么该serverTrust就真的不能通过验证了
return NO;
} switch (self.SSLPinningMode) {
// 理论上,上面那个部分已经解决了self.SSLPinningMode)为AFSSLPinningModeNone)等情况,所以此处再遇到,就直接返回NO
case AFSSLPinningModeNone:
default:
return NO;
// 这个模式表示用证书绑定(SSL Pinning)方式验证证书,需要客户端保存有服务端的证书拷贝
// 注意客户端保存的证书存放在self.pinnedCertificates中
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
// 这里使用SecCertificateCreateWithData函数对原先的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 将pinnedCertificates设置成需要参与验证的Anchor Certificate(锚点证书,通过SecTrustSetAnchorCertificates设置了参与校验锚点证书之后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书),具体就是调用SecTrustEvaluate来验证。
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); if (!AFServerTrustIsValid(serverTrust)) {
return NO;
} // 服务器端的证书链,注意此处返回的证书链顺序是从叶节点到根节点
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
// 从服务器端证书链的根节点往下遍历,看看是否有与客户端的绑定证书一致的,有的话,就说明服务器端是可信的。因为遍历顺序正好相反,所以使用reverseObjectEnumerator
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]){
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
} return NO;
}
// AFSSLPinningModePublicKey模式同样是用证书绑定(SSL Pinning)方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = ;
// 从serverTrust中取出服务器端传过来的所有可用的证书,并依次得到相应的公钥
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
// 依次遍历这些公钥,如果和客户端绑定证书的公钥一致,那么就给trustedPublicKeyCount加一
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += ;
}
}
}
// trustedPublicKeyCount大于0说明服务器端中的某个证书和客户端绑定的证书公钥一致,认为服务器端是可信的
return trustedPublicKeyCount > ;
}
} return NO;
}

上述函数实现中调用了AFSecurityPolicy的私有方法(注意evaluateServerTrust:forDomain:方法是AFSecurityPolicy比较重要的公开方法),下面我来逐个分析相应的函数实现。

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust)

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
// 对照下面的Require_noErr_Quiet函数解释,此处errorCode指的就是SecTrustEvaluate(serverTrust, &result)函数的返回值。如果serverTrust评估出错,那么就直接执行return isValid,默认isValid为NO。
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
// 如果SecTrustEvaluate函数评估没出错,那么就看result的结果
// 只有当result为kSecTrustResultUnspecified(此标志表示serverTrust评估成功,此证书也被暗中信任了,但是用户并没有显示地决定信任该证书),或者当result为kSecTrustResultProceed(此标志表示评估成功,和上面不同的是该评估得到了用户认可),这两者取其一就可以认为对serverTrust评估成功
// 在下面有对result类型(SecTrustResultType)的简单讲解
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); _out:
return isValid;
} // Require_noErr_Quiet是一个宏定义函数,表示如果errorCode不为0(0表示没有错误),那么就使用C语言中的go语句,跳到对应exceptionLabel地方开始执行代码。
#ifndef __Require_noErr_Quiet
#define __Require_noErr_Quiet(errorCode, exceptionLabel) \
do \
{ \
if ( __builtin_expect( != (errorCode), ) ) \
{ \
goto exceptionLabel; \
} \
} while ( )
#endif // kSecTrustResultUnspecified和kSecTrustResultProceed都是SecTrustResultType类型
/**
SecTrustResultType中枚举值的含义包括两个方面:一个是指评估是否成功,另一个是指该评估结果是不是由用户决定的。对于是不是由用户决定的这个问题,上面kSecTrustResultUnspecified和kSecTrustResultProceed就是一个很好的例子
*/

static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust)

// 获取到serverTrust中证书链上的所有证书
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
// 使用SecTrustGetCertificateCount函数t获取到serverTrust中需要评估的证书链中的证书数目,并保存到certificateCount中
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
// 使用SecTrustGetCertificateAtIndex函数获取到证书链中的每个证书,并添加到trustChain中,最后返回trustChain
for (CFIndex i = ; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
} return [NSArray arrayWithArray:trustChain];
}

static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust)

// 取出serverTrust中证书链上每个证书的公钥,并返回对应的该组公钥
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
// 接下来的一小段代码和上面AFCertificateTrustChainForServerTrust函数的作用基本一致,都是为了获取到serverTrust中证书链上的所有证书,并依次遍历,取出公钥。
SecPolicyRef policy = SecPolicyCreateBasicX509();
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = ; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i); SecCertificateRef someCertificates[] = {certificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, , NULL); SecTrustRef trust;
// 根据给定的certificates和policy来生成一个trust对象
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out); SecTrustResultType result;
// 使用SecTrustEvaluate来评估上面构建的trust
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
// 如果该trust符合X.509证书格式,那么先使用SecTrustCopyPublicKey获取到trust的公钥,再将此公钥添加到trustChain中
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)]; _out:
// 注意释放资源
if (trust) {
CFRelease(trust);
} if (certificates) {
CFRelease(certificates);
} continue;
}
CFRelease(policy);
// 返回对应的一组公钥
return [NSArray arrayWithArray:trustChain];
}

2.2 + [AFSecurityPolicy policyWithPinningMode:withPinnedCertificates:]

AFSecurityPolicy中还有一些关于初始化的函数,比较重要的就数+ [AFSecurityPolicy policyWithPinningMode:withPinnedCertificates:]这个函数了。

// 初始化AFSecurityPolicy对象的SSLPinningMode和pinnedCertificates两个属性
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode; [securityPolicy setPinnedCertificates:pinnedCertificates]; return securityPolicy;
} // 此函数设置securityPolicy中的pinnedCertificates属性
// 注意还将对应的self.pinnedPublicKeys属性也设置了,该属性表示的是对应证书的公钥(与pinnedCertificates中的证书是一一对应的)
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
_pinnedCertificates = pinnedCertificates; if (self.pinnedCertificates) {
NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
for (NSData *certificate in self.pinnedCertificates) {
id publicKey = AFPublicKeyForCertificate(certificate);
if (!publicKey) {
continue;
}
[mutablePinnedPublicKeys addObject:publicKey];
}
self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
} else {
self.pinnedPublicKeys = nil;
}
}

static id AFPublicKeyForCertificate(NSData *certificate)

// 此函数没什么特别要提及的,和AFPublicKeyTrustChainForServerTrust实现的原理基本一致
// 区别仅仅在该函数是返回单个证书的公钥(所以传入的参数是一个证书),而AFPublicKeyTrustChainForServerTrust返回的是serverTrust的证书链中所有证书公钥
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
// 因为此处传入的certificate参数是NSData类型的,所以需要使用SecCertificateCreateWithData来将NSData对象转化为SecCertificateRef对象
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out); allowedCertificates[] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, , NULL); policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out); allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust); _out:
if (allowedTrust) {
CFRelease(allowedTrust);
} if (policy) {
CFRelease(policy);
} if (tempCertificates) {
CFRelease(tempCertificates);
} if (allowedCertificate) {
CFRelease(allowedCertificate);
} return allowedPublicKey;
}

3. AFNetworkReachabilityManager


AFNetworkingReachabilityManager是我使用AFNetworking的时候,第一个接触到的类。当时主要是用这个类做一些网络状态判断。比如我当时做一个视频类的app时,考虑到网络如果是2G/3G/4G,那么最好是有一个switchButton来让用户选择是否使用2G/3G/4G来观看在线视频,此时你就需要判断当前网络状态,如果用户不允许2G/3G/4G网络观看在线视频,而此时手机网络又是2G/3G/4G网络,就得提示用户当前网络不支持播放在线视频。

该类的使用方法很简单,我们看一个简单例子:

// 设置networkReachabilityStatusBlock,根据不同网络状态,用户自定义处理方式
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"network status '%@'", AFStringFromNetworkReachabilityStatus(status));
}];
// 启动网络监听
[manager startMonitoring];

其实networkReachabilityStatusBlock应该都好理解。关键是这个startMonitoring方法。大家是不是觉得很神奇,感觉就像app在系统后台有一个循环,不停地检测网络状态。那么具体是怎么做到的呢,这里先透露点:代码的核心使用了iOS提供的一系列SCNetworkReachability文件中的方法。

- (void)startMonitoring {
// 先停止之前的网络监听
[self stopMonitoring];
// networkReachability表示的是需要检测的网络地址的句柄
if (!self.networkReachability) {
return;
} __weak __typeof(self)weakSelf = self;
// 根据网络状态status来设置网络状态监听的回调函数callback
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
} };
/** context是一个结构体
typedef struct {
// 创建一个SCNetworkReachabilityContext结构体时,需要调用SCDynamicStore的创建函数,而此创建函数会根据version来创建出不同的结构体,SCNetworkReachabilityContext对应的version是0
CFIndex version;
// 下面两个block(release和retain)的参数就是info,此处表示的是网络状态处理的回调函数
void * __nullable info;
// 该retain block用于对info进行retain,下面那个AFNetworkReachabilityRetainCallback核心就是调用了Block_copy(用于retain一个block函数,即在堆空间新建或直接引用一个block拷贝)
const void * __nonnull (* __nullable retain)(const void *info);
// 该release block用于对info进行release,下面那个AFNetworkReachabilityReleaseCallback核心就是调用了Block_release(用于release一个block函数,即将block从堆空间移除或移除相应引用)
void (* __nullable release)(const void *info);
// 提供info的description,此处调用为NULL
CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;
*/
SCNetworkReachabilityContext context = {, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
/**
// 给客户端指定对应target(该参数和需要检测网络状况的地址有一定关联,此处使用的是self.networkReachability),然后当这个target的网络状态变化时,告之SCNetworkReachabilityCallBack对象callout处理(此处使用的是AFNetworkReachabilityCallback),另外callout中使用到的参数包括target和context提供的info。
Boolean
SCNetworkReachabilitySetCallback (
SCNetworkReachabilityRef target,
SCNetworkReachabilityCallBack __nullable callout,
SCNetworkReachabilityContext * __nullable context
) __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);
*/
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
/**
此处表示在main RunLoop中以kCFRunLoopCommonModes形式处理self.networkingReachability
*/
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
// 在后台检测self.networkingReachability的网络状态,并使用SCNetworkReachabilityGetFlags函数返回产生的flag,注意此处flag表示的就是网络的状态,后面会详细介绍每种flag对应的状态是什么
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, ),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
// AFPostReachabilityStatusChange函数就是先将flags转化为对应的AFNetworkReachabilityStatus变量,然后给我们的callback处理,后面会详解此函数
AFPostReachabilityStatusChange(flags, callback);
}
});
}

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block)

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
// 使用AFNetworkReachabilityStatusForFlags函数将flags转化为status,提供给下面block使用
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(status);
}
// 对于用户,可以使用KVO来观察status的变化,随后用户可以根据传过来的userInfo[AFNetworkingReachabilityNotificationStatusItem]获取到相应的status
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
});
} static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
// 该网络地址可达
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != );
// 该网络地址虽然可达,但是需要先建立一个connection
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != );
// 该网络虽然也需要先建立一个connection,但是它是可以自动去connect的
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != ) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != ));
// 不需要用户交互,就可以connect上(用户交互一般指的是提供网络的账户和密码)
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == );
// 如果isReachable==YES,那么就需要判断是不是得先建立一个connection,如果需要,那就认为不可达,或者虽然需要先建立一个connection,但是不需要用户交互,那么认为也是可达的
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction)); // AFNetworkReachabilityStatus就四种状态Unknown、NotReachable、ReachableViaWWAN、ReachableViaWiFi,这四种状态字面意思很好理解,这里就不赘述了
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
if (isNetworkReachable == NO) {
status = AFNetworkReachabilityStatusNotReachable;
}
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != ) {
status = AFNetworkReachabilityStatusReachableViaWWAN;
}
#endif
else {
status = AFNetworkReachabilityStatusReachableViaWiFi;
} return status;
}

此时AFNetworking如何判断网络状态的思路基本也理清楚了:先使用SCNetworkReachability相关函数得到网络状态,不过此时的网络状态还需要放到AFNetworking中封装一层,以提供适合用户使用的API(如isReachable、isReachableViaWWAN、isReachableViaWiFi),对于不同的网络状态,用户只需要定义自己的block进行处理就行

最后,不知道大家对_networkReachability这个属性值是否有疑惑:源码中定义了一个sharedManager,那么sharedManager中的_networkReachability是如何设置的呢?一图以蔽之,我就不赘述了。

4. 总结


这一篇作为AFNetworking源码阅读系列的最后一篇文章到此结束了。虽然还有部分UIKit+AFNetworking的内容没说,但是基本也没什么难度了,图片部分可以参考我之前写的SDWebImage源码分析。而其他UIKit部分,参考AFNetworking源码阅读第一篇文章中对AFNetworkActivityIndicatorManager和UIRefreshControl+AFNetworking的解读,因为基本思路都是一样的。

5. 参考文章


【原】AFNetworking源码阅读(六)的更多相关文章

  1. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  2. 【原】AFNetworking源码阅读(四)

    [原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...

  3. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

  4. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  5. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

  6. Spark常用函数(源码阅读六)

    源码层面整理下我们常用的操作RDD数据处理与分析的函数,从而能更好的应用于工作中. 连接Hbase,读取hbase的过程,首先代码如下: def tableInitByTime(sc : SparkC ...

  7. 【AFNetworking】AFNetworking源码阅读(一)

    1. 前言 2. iOS Example代码结构 3.AFNetworkActivityIndicatorManager 4. UIRefreshControl+AFNetworking 5. AFN ...

  8. AFNetworking源码阅读

    get方法: - (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters progress:(void ...

  9. (原)NSQ源码阅读和分析(1)

    原文出处:https://www.cnblogs.com/lihaiping/p/12324371.html 本文记录自己在阅读和学习nsq源码的时候的一些学习笔记,主要目的是个人总结和方便后期查阅. ...

随机推荐

  1. 【.net 深呼吸】细说CodeDom(1):结构大观

    CodeDom 是啥东东?Html Dom听过吧,XML Dom听过吧.DOM一般可翻译为 文档对象模型,那 Code + DOM呢,自然是指代码文档模型了.如果你从来没接触过 CodeDom,你大概 ...

  2. EasyMesh - A Two-Dimensional Quality Mesh Generator

    EasyMesh - A Two-Dimensional Quality Mesh Generator eryar@163.com Abstract. EasyMesh is developed by ...

  3. RxJS + Redux + React = Amazing!(译一)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...

  4. Android公共title的应用

    我们在开发Android应用中,写每一个页面的时候都会建一个title,不是写一个LinearLayout就是写一个RelativeLayout,久而久之就会觉得这样繁琐,尤其几个页面是只是标题不一样 ...

  5. Java 经典入门(一)

    一.什么是 Java 技术?为何需要 Java? Java 是由 Sun Microsystems 在 1995 年首先发布的编程语言和计算平台.有许多应用程序和 Web 站点只有在安装 Java 后 ...

  6. C++标准库实现WAV文件读写

    在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库. WAV文件结构 ...

  7. SAP CRM 性能小技巧

    导言 本页面打算收集SAP CRM实施中可以用于避免性能问题的注意事项,重要的事项会由图标标识. 如果你有其他的技巧想要说出来,别犹豫! 性能注意事项 通用 缓存读取类访问,特别是在性能关键的地方,比 ...

  8. Dynamics CRM 之ADFS 使用 WID 的联合服务器场

    使用 WID 的联合服务器场 默认拓扑 Active Directory 联合身份验证服务 (AD FS) 是联合服务器场,使用 Windows 内部数据库 (WID). 在这种拓扑, AD FS 使 ...

  9. 当 IDENTITY_INSERT 设置为 OFF 时,不能为表 'T_Shell' 中的标识列插入显式值。

    --允许将显示值插入表的标识列中-ON:允许 OFF:不允许set identity_insert T_shell ONset identity_insert T_Shell OFF

  10. windows 7(32/64位)GHO安装指南(系统安装篇)~重点哦!!~~~~

    经过了前三篇的铺垫,我们终于来到了最重要的部分~~如果没看过前几篇的小伙伴们,可以出门右转~~用十几分钟回顾一下~~然后在看这篇会感觉不一样的~~~~ 下面让我们来正式开始吧 我们进入大白菜的桌面是酱 ...