最近重读了AFNetworking 3.x的源码,算是温故而知新吧。也梳理了一些优秀的代码细节和面试考点,罗列下来,发现这个库小而精致,简直初学者的宝藏库。程序员
先说个题外话,阅读优质的开源代码库,绝对是程序员们快速提高自个人有效途径,而怎样高效率的去阅读源码一样也是一个问题,不知道有没有人和我以前同样,碰到过读却是读了,但总感受收获不大的状况。面试
这里分享一下个人一些读码经验:json
多思考,多抛出问题,好比说segmentfault
关于AFNetworking
的一些优秀代码细节,我这里也整理了一部分,能够查阅后文设计模式
参考费曼学习法,我认为这一点是最好的加深理解和强化记忆的手段。随着年龄的增大,记忆力会有所衰退,有个笔记可以回顾,能节约大把再次记忆的时间。此外,多与人分享还可以提高本身的影响力,与人交流验证,也可以为本身查缺补漏。api
仍是说回到AFNetworking
这里,AF的代码结构大部分人应该都了解,这里我简单介绍下。AFNetworking 3.x
的代码结构比2.x要简单许多,主要也得益于苹果优化了网络相关的api,总体代码有这么几部分:浏览器
这里就是AF代码的核心了,主要负责网络请求的发起,回调处理,是在系统网络相关API上的一层封装。大部分逻辑是在AFURLSessionManager
里面处理的,AFHTTPSessionManager
则是专为HTTP请求提供了一些便利方法。若是须要扩展其余协议的功能(好比FTP协议),能够考虑从AFURLSessionManager
建立一个子类。安全
这两兄弟主要处理一些参数序列化相关的工做。AFURLRequestSerialization
是将传入的参数构形成NSURLRequest
,好比自定义的header,一些post或者get参数等等。 AFURLResponseSerialization
主要是将系统返回的NSURLResponse
处理成咱们须要的responseObject,好比json、xml、image等等服务器
处理https相关的公钥和验证逻辑。目前因为苹果ATS的开启,基本HTTPS已经成为标配。虽然一般直接使用CA来验证服务器公钥的状况下,不须要咱们额外作什么配置。可是从这里出发,顺便考察一下HTTPS相关的知识点,感受也比较常见,具体面试题可看下文微信
这个实际上是比较独立的一个模块了,提供获取当前网络状态的功能。
这里主要是经过Category来提供了一下UIkit的便利方法
仔细瞅瞅代码以后,发现常见的OC基础知识在AF里面都有具体应用,挺多仍是面试题考点,这里也是作个记录和梳理。
+ (instancetype)defaultInstance { static AFImageDownloader *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; }
必知必会,经过dispatch_once来保证多线程调用时,只有一个实例被建立。
// 注意是并行队列 self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT); // 串行写,注意barrier block的具体执行时机 dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{ [self.mutableHTTPRequestHeaders setValue:value forKey:field]; }); // 并行读,注意和上面写操做同时执行时的执行顺序 NSDictionary __block *value; dispatch_sync(self.requestHeaderModificationQueue, ^{ value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; });
必知必会,GCD使用barrier来处理并行读串行写问题的具体用法
__weak __typeof(self)weakSelf = self; AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } return strongSelf; };
必知必会,weakSelf避免循环引用,strongSelf保证block内部执行过程当中self不会被释放
NSSecureCoding、kvo、swizzle、NSStream、NSProgress、代码注释、pragma mark等等
前面提到阅读开源库时,要多思考多提问题,这里也结合一些面试考题来梳理下
这个知识点应该是AF2.x版本面试官比较喜欢问的了,AF2.x版本有个细节,经过runloop开启了一个常驻子线程,具体代码是这样的:
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; }
首先,咱们要了解为什么要开启常驻子线程?
NSURLConnection
的接口是异步的,而后会在发起的线程回调。而一个子线程,在同步代码执行完成以后,通常状况下,线程就退出了。那么想要接收到NSURLConnection
的回调,就必须让子线程至少存活到回调的时机。而AF让线程常驻的缘由是,当发起多个http请求的时候,会统一在这个子线程进行回调的处理,因此干脆就让其一直存活下来。
上面说的通常状况,子线程执行完任务就会退出,那么什么状况下,子线程可以继续存活呢?这就涉及到第二个问题了,AF是如何开启常驻线程的,这里实际上考察的是runloop的基础知识。
关于runloop,推荐看下《深刻理解RunLoop》,我的以为讲得很是详尽。这里简单来讲,当runloop发现还有source/timer/observer的时候,runloop就不会退出。因此AF这里就经过给当前runloop添加一个NSMachPort,这个port实际上相对于添加了一个source事件源,这样子线程的runloop就会一直处于循环状态,等待别的线程向这个port发送消息,而实际上AF这里是没有消息发送到这个port的。
除了AF的这种处理方式,实际上苹果也提供了回调线程的解决方案:
// NSURLConnection - (void)setDelegateQueue:(nullable NSOperationQueue*) queue // NSURLSession + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
苹果提供了接口,可让你制定一个operationQueue供回调执行。因此AF3.x的时候,就直接建立了一个并发度为1的队列,来处理回调。
扩展问题:
既然提到了runloop,能够考虑一下runloop的扩展问题:
咱们若是仔细查看代码,应该就能得出这样的结论:manager与session是1对1的关系,AF会在manager初始化的时候建立对应的NSURLSession。一样,AF也在注释中写明了能够提供一个配置好的manager单例来全局复用。
那么复用manager实际上就是复用了session,而复用session能够带来什么好处呢?
其实iOS9以后,session就开始支持http2.0。而http2.0的一个特色就是多路复用(可参考《Http系列(二) Http2中的多路复用》)。因此这里复用session其实就是在利用http2.0的多路复用特色,减小访问同一个服务器时,从新创建tcp链接的耗时和资源。
官方文档也推荐在不一样的功能场景下,使用不一样的session。好比:一个session处理普通的请求,一个session处理background请求;1个session处理浏览器公开的请求,一个session专门处理隐私请求等等场景。
如今,因为苹果ATS的策略,基本都切到HTTPS了,HTTPS的基本原理仍是须要了解一下的,这里不作介绍,须要的能够google查阅一下相关文章。
一般,首先咱们要了解中间人攻击,大致就是黑客经过截获服务器返回的证书,并伪形成本身的证书,一般咱们使用的Charles/Fiddler等工具实际上就能够当作中间人攻击。
解决方案其实也很简单,就是SSL Pinning
。AFSecurityPolicy
的AFSSLPinningMode
就是相关设置项。
SSL Pinning
的原理就是须要将服务器的公钥打包到客户端中,tls验证时,会将服务器的证书和本地的证书作一个对比,一致的话才容许验证经过。
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) { AFSSLPinningModeNone, AFSSLPinningModePublicKey, // 只验证证书中的公钥 AFSSLPinningModeCertificate, // 验证证书全部字段,包括有效期以内 };
因为数字证书存在有效期,内置到客户端后就存在失效后致使验证失败的问题,因此能够考虑设置为AFSSLPinningModePublicKey
的模式,这样的话,只要保证证书续期后,证书中的公钥不变,就可以经过验证了。
扩展:
用了SSL Pinning
就安全了吗?
参考文章:
别说你会AFNetworking3.0/NSURLSession
AFNetWorking源码之AFSecurityPolicy
原文连接: http://www.luoyibu.cn/posts/3...
欢迎扫码关注个人微信公众号