分析AFN主要设计思路 - 强干弱枝
至于源码,此文章只从零到一探讨了解方法,先主干,再脉络,再再细枝一步步细化。
其实就跟拍电影一样,很多经典先从一个维度讲一遍故事,然后分别切入不同的视角,同样的故事再讲多遍,就就很深刻了。
为了尽可能细节阐述思想,案例采用OC展示
破题 - 展示一张图片
出错 - 未指定content类型 content-type: image/jpeg
修正
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes =
[NSSet setWithObjects:@"image/jpeg", nil];
然后就,图片正常加载
更直接一些,set一张高清图片
[mImageView setImageWithURL:[NSURL URLWithString:imgUrlStr]];
一样加载出来高清图片展示,那么 setImageWithURL 究竟做了什么,这是目前需要探究的入口
如果目前自己设计一个图片下载展示工具,会设计成什么样子?可以做个猜想,简单推想一下
- 如何缓存图片
- 缓存空间管理
- 管理缓存 需要对图片进一步封装处理
- manager,管理增删查改,如何对缓存进行更新
- 缓存空间管理
- 如何对图片的请求任务撤销处理
- 项目中多处请求相同的图片如何复用,是否多个异步请求发生
- 如果多次请求都发生,要实现复用,又该如何响应
不妨简单模拟一个流程先
根据大致的猜想流程,转变为代码还有一段距离。
缓存的存储设计实现,包括缓存的管理? 相同请求复用队列设计实现,一个请求,响应多个源? 相同url,不同图片,如何做到更新?
相信只要解决以上问题,一个相对标准化的图片请求加载器就算完成了!
入手源码开始
根据入口源码调用,结合之前的猜想,得到一些信息:
- 缓存管理通过下载器获取,并非设计成独立的缓存中心
- 出现了Task类,也就是设计了任务的概念
- 缓存图片通过NSURLRequest 来获取,request里存储了一些不变的信息来定位缓存图片
- ImageView 调用api,success回调是nil,说明加载图片的逻辑本身在缓存下载器内部就已经做了,为了使用者更方便,如果想自定义可以自己设置上回调参数,做一些自己监听业务。
可以作为自己封装的一个准则,一方面让别人最简单最直接使用,同时也流出让别人深入的空间,什么东西别做那么死板
读取cache逻辑
imageCache读取到图片,success回调
同时清除干净下载请求信息
下载器是通过关联对象 or 单例获取,而缓存通过下载器拿到,这就保障了实时操作同一份缓存空间,达到了缓存的目的,
查看缓存的操作方法有哪些
缓存设计了 增加 删除 查找方法,同时还有一个 开关逻辑 - 是否需要缓存的外放设置
下载器逻辑
先入为主的时候,往往先看返回值本身,不要过早嵌入进回调内部,容易迷失
此时,得到了一个 AFImageDownloadReceipt: af_activeImageDownloadReceipt,根据注释,是用来取消task的
图中可见,外层是一个 串型同步队列
- 因为缓存不可能无限制存放,过期需要删除,删除需要有先后,删除较旧的,保留较新的;
- 请求数量也不是无节制的,不同步处理,没办法保障最大请求数限制
mergedTasks也是通过下载器来获取的,与缓存器不同的是,缓存器public,mergedTasks是private,这个细节说明,作者是不希望使用的人介入任务的,属于封装内部
通过url作为唯一标识,标识相同的请求,从 mergedTasks字典中根据标识取出task,新创建response handler,并添加到task中的responseHandlers数组里,此时可以看出 task是对NSURLSessionDataTask 的封装处理
,额外添加了标识
信息,responseHandlers
信息
举个例子 - app一个页面的多个cell均请求到一张图,后请求加载的cell成功回调就会被缓存到一个队列里,这个队列里存放了很多先于自己的成功回调,这样就达到了一份相同的资源,可以响应多个加载图片请求,而不用频繁多次请求相同的资源。 此时如果网络不好或者图片资源较大,而除了开始加载的图片位置反应稍慢些,别的地方的图片瞬间就加载出来了,耗时加载的view等待时机,从另一个方面看,成就了别的想要跟自己一样的上层view,因为直接复用了内存,而且也达到了内容同步一致,小小的策略,影响反而是巨大的。
设置request.cachePolicy,可以设置不缓存/缓存策略 还体现了一个细节,就是下载器任务阶段,突然来了一个从缓存获取,之前不是已经判断过了么,正因为缓存还没有,走下载器,此时这个操作有点跳脱。 这个疑问也是因为我们太习惯了线形考虑问题,如果存在线程问题,这个操作就很合理了
- 封装NSURLSessionDataTask ---> AFImageDownloaderMergedTask:mergedTask
- 新创建responsehandler
- 添加 新创建的responsehandler 到 mergedTask中的 responseHandlers数组里
以下查看 NSURLSessionDataTask 创建逻辑,看AFNetworkSessionManager 做了哪些处理
一个个delegate通过 task存储到字典里, 溯源通过字典获取delegate,继而拿到task,通过task回调 uploadProgress downloadedProgress
为每个task设置observer(sessionManager) 监听 taskDidResume/taskDidSuspend
目前为止,代码一层层探查得差不多了,但只是取值赋值,具体task handler回调怎么个进行,貌似中断了,别忘了,ios动态特性
这明显是imp给替换了
此时跟 ④
衔接上
Foundation - NSURLSessionDownloadDelegate回调
[task resume] 之后,剩下的NSURLSession 执行网络底层逻辑
根据 NSURLSessionDownloadDelegate回调,此时交给AFURLSessionManager 处理
通过 completionHandler回调
回到了开始 创建task的 completionHandler 内部逻辑
简单总结下回调流程
网络下载完成,回到cache处理
- 清除task
- 缓存下载下来的图片
- 回调返回数据
缓存图片
已图片缓存容量超过偏好设置容量 处理(⚠️ 缓存图片不能删除半张
)
图片大小获取