AFNetworking源码分析

说到AFNetwokring这个强大第三方网络请求库,大家应该都不陌生吧,ios开发、mac开发都经常用,主要是他使用起来简单、方便。下面我们看看他的源码,来探讨一下吧。

首先,我们一起来看一下它的框架的组成部分吧。


上面的图片我从AFNetworking的文件中接的图,我们可以看出,包含5个部分,其实AFHTTPSessionManager是AFURLSessionManager的子类,所以说它的组成是四个部分:

网络通信模块(AFHTTPSessionmanager、AFURLSessionManager)

网络状态监听模块(AFNetworkReachabilityManager)

网络通信信息序列化反序列化策略模块(AFURLRequestSErialization、AFURLResponseSerialization)

网络通信安全策略模块(AFSecurityPolicy)

对ios UIKit库的拓展

其核心当然就是网络通信模块AFURLSessionManager,这个类是对NSURLSession的进一步的封装

其他模块均是配合网络通信或对已有UIKIt的扩展

一、初始化方法

最终都会到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中来了。

这个方法主要做了什么事情呢?

1、调父类的- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 这个方法里面设置了

队列为非主线程队列,队列的并发数为1,

初始化了session,设置了代理,

初始化了网络通信监听、网络安全策略

,初始化了保存网络请求任务、对应的任务代理到字典中 

遍历session中的任务,将任务对应的任务代理设为nil,请求代理

2、设置默认AFURLRequestSErialization、AFURLResponseSerialization,分别是请求序列对象和响应序列对象,这个两个东西会在NSURLRequest中设置请求头、请求体等一些信息中用到

上面的分析,可以看到,主要的东西还在父类中做的,里面初始化方法里便利session,将session中任务的代理清空是一种防御性编程。

然后来看GET请求

- (NSURLSessionDataTask *)GET:(NSString *)URLString参数:(ID)参数进度:(void(^)(NSProgress * _Nonnull))downloadProgress成功:(无效(^)(NSURLSessionDataTask * _Nonnull,id _Nullable))成功失败:(void(^)(NSURLSessionDataTask * _Nullable,NSError * _Nonnull))失败

{//生成一个任务

NSURLSessionDataTask * dataTask = [self dataTaskWithHTTPMethod:@“GET”URLString:URLString参数:参数上传进度:无downloadProgress:downloadProgress成功:成功失败:失败];

//开始网络请求[dataTask resume];返回dataTask;}

这里主要生成一个NSURLSessionDataTask来进行网络请求

我们继续往父类里看,看看这个方法到底做了什么:

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString参数:(ID)参数uploadProgress:(可为空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失败:(void(^)(NSURLSessionDataTask *,NSError *))失败{NSError * serializationError = nil;

//把参数,还有各种东西转化为一个请求

NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

If(serializationError){如果(失败)

{#pragma clang诊断推送#pragma clang诊断忽略“-WgnU”//如果解析错误,直接返回dispatch_async(self.completionQueue?:dispatch_get_main_queue(),^ {失败(nil,serializationError);});

#pragma clang诊断流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上传进度:上传进度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject,NSError * error){如果(错误){如果(失败){失败(dataTask,错误);}} else {如果(成功){成功(dataTask,responseObject);}}}];返回dataTask;}

这个

这个方法做了两件事:

1、用self.requestSerializer和各种参数去获取一个最终网络请求需要的NSMutableURLRequest

2、调用另外一个方法dataTaskWithRequest去拿到我们需要NSURLSessionDataTask,并且在回掉中调用成功或失败的回掉

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString参数:(ID)参数uploadProgress:(可为空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *,id))成功失败:(void(^)(NSURLSessionDataTask *,NSError *))失败{NSError * serializationError = nil;//把参数,还有各种东西转化为一个请求NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];if(serializationError){如果(失败){#pragma clang诊断推送#pragma clang诊断忽略“-WgnU”//如果解析错误,直接返回dispatch_async(self.completionQueue?:dispatch_get_main_queue(),^ {失败(nil,serializationError);});#pragma clang诊断流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上传进度:上传进度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject,NSError * error){如果(错误){如果(失败){失败(dataTask,错误);}} else {如果(成功){成功(dataTask,responseObject);}}}];返回dataTask;}

我们继续往下看:当解析错误,我们直接调用failuer失败模块回去,里面有个self.completionQueue,这是我们自定义的,这个是gcd队列,如果设置就从这个队列中回掉了,不从主队列中回掉了

实际上这个队列还是很有用,有些公司有自己的一套数据加密解密解析模式,所以我们回掉过来的数据并不想在主队列,我们可以在这个队列对数据进行解析,然后在回到主线程中。

(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)请求uploadProgress:(可为空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(可空(void)(^)(NSURLResponse *响应,id _Nullable响应对象,NSError * _Nullable错误))completionHandler {__block NSURLSessionDataTask * dataTask = nil;//第一件事,创建NSURLSessionDataTask,里面适配了iOS8上以下taskIdentifiers,函数创建任务对象。//其实现应该是因为iOS 8.0以下版本中会并发地创建多个任务对象,而同步有没有好,导致taskIdentifiers不唯一...这边做了一个串行处理url_session_manager_create_task_safely(^ {dataTask = [self.session dataTaskWithRequest:request];});[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];返回dataTask;}

我们注意到这个方法非常简单,就调用了一个url_session_manager_create_task_safely()函数,传了一个块进去座里就是iOS的原生生成dataTask的方法。此外,还调用了一个addDelegateForDataTask的方法。

我们到这先到这个函数里去看看:

static void url_session_manager_create_task_safely(dispatch_block_t block){if(NSFoundationVersionNumber

方法非常简单,关键是理解这么做的目的:为什么我们不直接去调用

dataTask = [self.session dataTaskWithRequest:request];

非要绕这么一圈,我们点进去错误日志里看看,原来这是为了适配iOS8上的以下,创建会话的时候,偶发的情况会出现会话的属性taskIdentifier这个值不唯一,而这个taskIdentifier是我们后面来映射代表的关键,所以它必须是唯一的。

具体原因应该是NSURLSession内部去生成任务的时候是用多线程并发去执行的。想通了这一点,我们就很好解决了,我们只需要在iOS8上以下同步串行的去生成任务就可以防止这一问题发生(如果还是不理解同步串行的原因,可以看看注释)。

题外话:很多同学都会抱怨为什么同步我从来用不到,看,有用到的地方了吧,很多东西不是没用,而只是你想不到怎么用。

我们接着看到:

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

调用到:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTaskuploadProgress:(可为空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(void(^)(NSURLResponse * response,id responseObject,NSError * error))completionHandler{AFURLSessionManagerTaskDelegate * delegate = [[AFURLSessionManagerTaskDelegate alloc] init];// AFURLSessionManagerTaskDelegate与AFURLSessionManager建立相互的关系delegate.manager = self;delegate.completionHandler = completionHandler;//这个taskDescriptionForSessionTasks用来发送开始和挂起通知的时候会用到,就是用这个值来张贴通知,来两者对应dataTask.taskDescription = self.taskDescriptionForSessionTasks;// *****将AF委托对象与dataTask建立关系[self setDelegate:delegate forTask:dataTask];//设置AF委托的上传进度,下载进度块。delegate.uploadProgressBlock = uploadProgressBlock;delegate.downloadProgressBlock = downloadProgressBlock;}

总结一下:

1)这个方法,生成了一个AFURLSessionManagerTaskDelegate,这个其实就是AF的自定义代理。我们请求传来的参数,都赋值给这个AF的代理了。

2)delegate.manager = self;代理把AFURLSessionManager这个类作为属性了,我们可以看到:

@属性(非原子,弱)AFURLSessionManager *管理器;

这个属性是弱引用的,所以不会存在循环引用的问题。

3)我们调用了[self setDelegate:delegate forTask:dataTask];

我们进去看看这个方法做了什么:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)委托forTask:(NSURLSessionTask *)任务{//断言,如果没有这个参数,调试下坠毁在这NSParameterAssert(任务);NSParameterAssert(代表);//加锁保证字典线程安全[self.lock lock];//将AF委托放入以taskIdentifier标记的词典中(同一个NSURLSession中的taskIdentifier是唯一的)self.mutableTaskDelegatesKeyedByTaskIdentifier [@(task.taskIdentifier)] = delegate;//为AF代表设置任务的进度监听[委托setupProgressForTask:任务];//添加任务开始和暂停的通知[self addNotificationObserverForTask:task];[self.lock解锁];}

这个方法主要就是把AF代理和任务建立映射,存在了一个我们事先声明好的字典里。

而要加锁的原因是因为本身我们这个字典属性是可变的,是线程不安全的。而我们对这些方法的调用,确实是会在复杂的多线程环境中,后面会仔细提到线程问题。

还有个[delegate setupProgressForTask:task];我们到方法里去看看:

- (void)setupProgressForTask:(NSURLSessionTask *)task {__weak __typeof __(task)weakTask = task;//拿到上传下载期望的数据大小self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;//将上传与下载进度和任务绑定在一起,直接取消挂起恢复进度条,可以取消...任务[self.uploadProgress setCaslable:YES];[self.uploadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任务取消];}];[self.uploadProgress setPausable:YES];[self.uploadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暂停];}];如果([self.uploadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.uploadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask简历];}];}[self.downloadProgress setCancellable:YES];[self.downloadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任务取消];}];[self.downloadProgress setPausable:YES];[self.downloadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暂停];}];如果([self.downloadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.downloadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask简历];}];}//观察任务的这些属性[任务addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))选项:NSKeyValueObservingOptionNew上下文:NULL];[任务addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))选项:NSKeyValueObservingOptionNew上下文:NULL];[任务addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesSent))选项:NSKeyValueObservingOptionNew上下文:NULL];[任务addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))选项:NSKeyValueObservingOptionNew上下文:NULL];//观察进度这两个属性[self.downloadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))选项:NSKeyValueObservingOptionNew上下文:NULL];[self.uploadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))选项:NSKeyValueObservingOptionNew上下文:NULL];}

这个方法也非常简单,主要做了以下几件事:

1)设置downloadProgress与uploadProgress的一些属性,并且把两个和任务的任务状态绑定在了一起。注意这两个都是NSProgress的实例对象,(这里可能又一群小伙楞在这了,这是个什么...)简单来说,这就是iOS7引进的一个用来管理进度的类,可以开始,暂停,取消,完整的对应了任务的各种状态,当进度进行各种操作的时候,任务也会引发对应操作。

2)给的任务和进度的各个属及添加志愿监听,至于监听了干什么用,我们接着往下看:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {//是任务如果([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]){//给进度条赋新值如果([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]){self.downloadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]){self.downloadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]){self.uploadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]){self.uploadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];}}//上面的赋新值会触发这两个,调用块回调,用户拿到进度else if([object isEqual:self.downloadProgress]){if(self.downloadProgressBlock){self.downloadProgressBlock(对象);}}else if([object isEqual:self.uploadProgress]){if(self.uploadProgressBlock){self.uploadProgressBlock(对象);}}}

方法非常简单直观,主要就是如果任务触发志愿,则给进度进度赋值,应为赋值了,所以会触发进步的志愿,也会调用到这里,然后去执行我们传进来的downloadProgressBlock和uploadProgressBlock。主要的作用就是为了让进度实时的传递。

主要是观摩一下大神的写代码的结构,这个解耦的编程思想,不愧是大神...

还有一点需要注意:我们之前的setProgress和这个志愿监听,都是在我们AF自定义的委托内的,是有一个任务就会有一个代表的所以说我们是每个任务都会去监听这些属性,分别在各自的AF代理内。看到这,可能有些小伙伴会有点乱,没关系。等整个讲完之后我们还会详细的去讲捋一捋经理,任务,还有AF自定义代理三者之前的对应关系。

到这里我们整个对任务的处理就完成了。

2,HTTPS认证- (void)URLSession:(NSURLSession *)会话didReceiveChallenge:(NSURLAuthenticationChallenge *)挑战completionHandler:(void(^)(NSURLSessionAuthChallengeDisposition disposition,NSURLCredential * credential))completionHandler{//挑战处理类型为默认/ *NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理NSURLSessionAuthChallengeUseCredential:使用指定的证书NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战* /NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;__block NSURLCredential * credential = nil;// sessionDidReceiveAuthenticationChallenge是自定义方法,用来如何应对服务器端的认证挑战如果(self.sessionDidReceiveAuthenticationChallenge){disposition = self.sessionDidReceiveAuthenticationChallenge(session,challenge,&credential);} else {//此处服务器要求客户端的接收认证挑战方法是NSURLAuthenticationMethodServerTrust//也就是说服务端需要客户端返回一个根据认证挑战的保护空间提供的信任(即challenge.protectionSpace.serverTrust)产生的挑战证书。//而这个证书就需要使用credentialForTrust:来创建一个NSURLCredential对象如果([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){//基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战如果([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]){//创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];//确定挑战的方式如果(凭证){//证书挑战处置= NSURLSessionAuthChallengeUseCredential;} else {//默认挑战唯一区别,下面少了这一步!处置= NSURLSessionAuthChallengePerformDefaultHandling;}} else {//取消挑战处置= NSURLSessionAuthChallengeCancelAuthenticationChallenge;}} else {//默认挑战方式处置= NSURLSessionAuthChallengePerformDefaultHandling;}}//完成挑战if(completionHandler){completionHandler(处置,凭证);}

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

推荐阅读更多精彩内容