AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力。但这究竟是怎么做到的呢?看完这篇文章就明白了。
前言
这篇我们会介绍 AFNetworking 中的3个UIKit中的分类。UIActivityIndicatorView UIRefreshControl UIImageView。读完本篇就能够明白控件是如何显示网络图片的。那么如果你有兴趣,可以尝试让一个控件的layer也能够加载网络图片。
提供的功能
我们解读源码不仅仅是了解内部实现原理,还要让开发者明白在这些分类中我能够使用那些功能,因此在这个 提供的功能 小结中,我会把这3个分类提供的功能罗列出来,即使不看下边的源码解读,也会有所收获。
UIActivityIndicatorView+AFNetworkingUIActivityIndicatorView的这个分类最简单,它只提供了一个方法:setAnimatingWithStateOfTask:只要给UIActivityIndicatorView一个 task UIActivityIndicatorView会根据数据的加载情况 自动 开始动画或者结束动画。UIRefreshControl+AFNetworkingUIRefreshControl的这个分类的使用跟上边的UIActivityIndicatorView+AFNetworking一模一样。UIImageView+AFNetworkingUIImageView是最常用的显示图片的控件。额外增加了 placeholderImage(替代图片) 这个属性和 success failure 这两个block来自定义一些事件。最后增加了两个取消某个状态下的图片下载的方法。我们看下边的图片就好了:

UIActivityIndicatorView+AFNetworking
This category adds methods to the UIKit framework's
UIActivityIndicatorViewclass. The methods in this category provide support for automatically starting and stopping animation depending on the loading state of a session task.这个分类增加了
UIActivityIndicatorView的一个方法。这个方法能够提供根据task自动开始和结束动画的功能
这个分类需要依赖 AFNetworking。需要监听AFNetworking中的网络状态的通知。按照通常的想法是,只要我监听了通知然后设置自己的状态就完事了。然而这并不是好的设计。一个控件的某项新的功能应该交给一个专门负责这个功能的人去完成,这才是好的设计。
因此我们给UIActivityIndicatorView扩展了一个属性af_notificationObserver,这个属性是专门处理上边说的事件的管理者。
好吧,我们写出伪代码:
- (通知监听者 *)af_notificationObserver {
return 通知监听者;
}
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
监听者根据task来做一些事;
}
这样写的好处是:当我们想扩展别的功能的时候,只需要在添加一个其他功能的负责人就可以,所有的逻辑都是负责人自己实现。这种思想简直完美。我们看 AFNetworking 中对上边伪代码的实现。相信大多数朋友应该知道,往分类中添加属性使用Runtime,不明白的可以看这篇 Objective-C runtime的常见应用.
- (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver {
AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
[[self af_notificationObserver] setAnimatingWithStateOfTask:task];
}
我们来看看这个af_notificationObserver有什么话要说呢?
UIActivityIndicatorView *activityIndicatorView既然让我来管理UIActivityIndicatorView,那就必须拿到这个控件才行。initWithActivityIndicatorView:我不可能凭空出现,通过这个方法创建我。setAnimatingWithStateOfTask:我就是通过这个方法来操控UIActivityIndicatorView的。
这么看来,这个af_notificationObserver只需要上边3个东东就足够了,那么我们就剩下setAnimatingWithStateOfTask:这个方法的实现了。
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
// 移除 AFNetworking 的通知
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
// task != nil
if (task) {
// task的状态不等于完成
if (task.state != NSURLSessionTaskStateCompleted) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
// 状态为运行中就开始,否则为停止
if (task.state == NSURLSessionTaskStateRunning) {
[self.activityIndicatorView startAnimating];
} else {
[self.activityIndicatorView stopAnimating];
}
#pragma clang diagnostic pop
// 移除 AFNetworking 的通知
[notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingTaskDidResumeNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidCompleteNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidSuspendNotification object:task];
}
}
}
#pragma mark -
- (void)af_startAnimating {
dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
[self.activityIndicatorView startAnimating];
#pragma clang diagnostic pop
});
}
- (void)af_stopAnimating {
dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
[self.activityIndicatorView stopAnimating];
#pragma clang diagnostic pop
});
}
UIImageView+AFNetworking
我们在 AFImageDownloader 那篇文章中提到过,要异步显示网络上的图片,就要把图片数据缓存下来才行。因此,要赋予UIImageView这项功能,就需要使用 AFImageDownloader 来获取图片数据。
不知道大家发现没有,像这张图片中的这些方法,
,我们只需要实现参数最多的那个方法就行了。这应该就是所谓的 尾调函数 吧。
首先我们先看看UIImageView扩展的一个属性af_activeImageDownloadReceipt,这个属性是图片依据
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@end
@implementation UIImageView (_AFNetworking)
- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}
- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
通过运行时为@selector(af_activeImageDownloadReceipt) 设置了关联值,同样的原理。 sharedImageDownloader 也是这么设置的
+ (AFImageDownloader *)sharedImageDownloader {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
#pragma clang diagnostic pop
}
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
在这里说下这个objc_setAssociatedObject方法,其中第二个参数是一个地址,因此我们可以用@selector
或者自定义一个全局的const字段,取它的地址。 看下边的例子,我为UIImageView扩展了一个属性abc。
static const NSString *abcde;
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@property (readwrite, nonatomic, strong)NSString *abc;
@end
@implementation UIImageView (_AFNetworking)
- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}
- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)setAbc:(NSString *)abc {
objc_setAssociatedObject(self, &abcde, abc, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)abc {
return objc_getAssociatedObject(self, &abcde);
}
我在使用的时候
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setValue:@"qwer" forKey:@"abc"];
NSString *str = [imageView valueForKey:@"abc"];
NSLog(@"%@",str);
--
- (void)cancelImageDownloadTask {
if (self.af_activeImageDownloadReceipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
[self clearActiveDownloadInformation];
}
}
- (void)clearActiveDownloadInformation {
self.af_activeImageDownloadReceipt = nil;
}
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest {
return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
来看这个核心方法,处理手法和之前的代码如出一辙,值得学习的是,核心方法中的判断比较详细。
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
// urlRequest 不正确
if ([urlRequest URL] == nil) {
// 取消下载任务
[self cancelImageDownloadTask];
// 赋值替代图片
self.image = placeholderImage;
return;
}
// 如果当前活动的下载和本下载一样,就返回
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
return;
}
// 取消之前的下载任务
[self cancelImageDownloadTask];
// 取出downloader
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
// 取出缓存
id <AFImageRequestCache> imageCache = downloader.imageCache;
//Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
if (cachedImage) {
// 如果写了success Block 就调动block,但不会给image赋值
if (success) {
success(urlRequest, nil, cachedImage);
} else {
self.image = cachedImage;
}
[self clearActiveDownloadInformation];
} else {
// 没有缓存的话,先设置替代图片
if (placeholderImage) {
self.image = placeholderImage;
}
__weak __typeof(self)weakSelf = self;
NSUUID *downloadID = [NSUUID UUID];
AFImageDownloadReceipt *receipt;
receipt = [downloader
downloadImageForURLRequest:urlRequest
withReceiptID:downloadID
success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (success) {
success(request, response, responseObject);
} else if(responseObject) {
strongSelf.image = responseObject;
}
[strongSelf clearActiveDownloadInformation];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
[strongSelf clearActiveDownloadInformation];
}
}];
self.af_activeImageDownloadReceipt = receipt;
}
}
方法不是最重要的,重要是梳理出这一整套的逻辑和想法,下面我们就来分析分析。
- 首先我们规定,使用这个分类每加载一次图片生成一个af_activeImageDownloadReceipt凭据,这个凭据一旦下载完成后,需要置为nil。
- 我们使用上边的这个最长的方法来加载图片。
- 我们先判断这个urlRequest是不是有效的。有效就继续往下走,无效的话取消之前的下载,然后赋值替代图片。说明如果urlRequest失效,同时也取消了之前的下载
- 好,到这里,说明urlRequest是正确的,那么我们再判断是不是现在下载的跟之前正在下载的URL是一样的?存在这样一种操作,我写了两个上边的方法
- 这一步要取消之前的下载任务
- 在缓存中取图片,如果图片存在,那么再看看是否设置了success,设置了就调用这个block,否则就使用替代图片。
- 请求失败处理方法同上边6.一样。
总结
通过对上边的方法的解读,我们就很容易的给别的控件添加异步加载功能了。使用上边的方法且改动很少的代码就能完成。
推荐阅读
AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy
AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache
AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking的更多相关文章
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...
- AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
- AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache
这篇我们就要介绍AFAutoPurgingImageCache这个类了.这个类给了我们临时管理图片内存的能力. 前言 假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来 ...
- AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...
- AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...
- AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
本篇是AFNetworking 3.0 源码解读的第五篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
随机推荐
- Python 小而美的函数
python提供了一些有趣且实用的函数,如any all zip,这些函数能够大幅简化我们得代码,可以更优雅的处理可迭代的对象,同时使用的时候也得注意一些情况 any any(iterable) ...
- iOS代码规范(OC和Swift)
下面说下iOS的代码规范问题,如果大家觉得还不错,可以直接用到项目中,有不同意见 可以在下面讨论下. 相信很多人工作中最烦的就是代码不规范,命名不规范,曾经见过一个VC里有3个按钮被命名为button ...
- [APUE]标准IO库(上)
一.流和FILE对象 系统IO都是针对文件描述符,当打开一个文件时,即返回一个文件描述符,然后用该文件描述符来进行下面的操作,而对于标准IO库,它们的操作则是围绕流(stream)进行的. 当打开一个 ...
- 【知识必备】一文让你搞懂design设计的CoordinatorLayout和AppbarLayout联动,让Design设计更简单~
一.写在前面 其实博主在之前已经对design包的各个控件都做了博文说明,无奈个人觉得理解不够深入,所以有了这篇更加深入的介绍,希望各位看官拍砖~ 二.从是什么开始 1.首先我们得知道Coordina ...
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- TYPESDK手游聚合SDK服务端设计思路与架构之一:应用场景分析
TYPESDK 服务端设计思路与架构之一:应用场景分析 作为一个渠道SDK统一接入框架,TYPESDK从一开始,所面对的需求场景就是多款游戏,通过一个统一的SDK服务端,能够同时接入几十个甚至几百个各 ...
- 用angular怎么缓存父页面数据
angular做单页面应用是一个比较好的框架,但是它有一定的入门难度,对于新手来说可能会碰到很多坑,也有许多难题,大部分仔细看文档,找社区是能解决的. 但有些问题也许资料比较少,最近遇到过一个要缓存父 ...
- 【JQ基础】DOM操作
内部插入:append() //向每个匹配的元素内部追加内容,可包含 HTML 标签 $(selector).append(function(index,html)) /*•index - 可选.接收 ...
- 微信硬件H5面板开发(二) ---- 实现一个灯的控制
在第一节中讲解了openApi的调用,这一篇讲一下如何实现一个灯的控制.就用微信提供的lamp例子来做,将代码扒下来(实在是没办法,没有示例),整合到自己的项目中.lamp源码:http://file ...
- 虚拟机体验之 KVM 篇
在上一篇中,我展示了虚拟机软件 QEMU 的使用.效果及其性能,同时也分析了不同用户对虚拟机的不同追求.但是不管是桌面用户还是企业级用户,对虚拟机软件的追求有一点是共同的,那就是性能.QEMU 是一个 ...