该文章阅读的AFNetworking的版本为3.2.0。
这个分类是为UIButton
添加异步加载网络图片的方法
1.接口文件
- 图片下载器的访问方法
/**
设置用于下载图片的图片下载器
*/
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;
/**
获取用于下载图片的图片下载器
*/
+ (AFImageDownloader *)sharedImageDownloader;
复制代码
- 为按钮设置图片的方法
/**
为按钮设置指定状态和指定图片链接的图片
*/
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url;
/**
为按钮设置指定状态、指定图片链接和指定占位图的图片
*/
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
为按钮设置指定状态、指定图片链接、指定占位图以及指定成功与失败回调block的图片
*/
- (void)setImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
复制代码
- 为按钮设置背景图片的方法
/**
为按钮设置指定状态和指定图片链接的背景图片
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url;
/**
为按钮设置指定状态、指定图片链接和指定占位图的背景图片
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
为按钮设置指定状态、指定图片链接、指定占位图以及指定成功与失败回调block的背景图片
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
复制代码
- 取消图片加载方法
/**
取消按钮在指定状态下的所有图片下载任务
*/
- (void)cancelImageDownloadTaskForState:(UIControlState)state;
/**
取消按钮在指定状态下的所有背景图片下载任务
*/
- (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state;
复制代码
2.实现文件
2.1.UIButton+_AFNetworking私有分类
2.1.1.image相关
- 静态字符
// 普通
static char AFImageDownloadReceiptNormal;
// 高亮
static char AFImageDownloadReceiptHighlighted;
// 已选
static char AFImageDownloadReceiptSelected;
// 禁用
static char AFImageDownloadReceiptDisabled;
复制代码
- 静态方法
/**
这个方法通过传入的控件状态返回对应的静态字符
*/
static const char * af_imageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFImageDownloadReceiptNormal;
}
}
复制代码
- 属性的访问方法
/**
通过Runtime的关联对象为分类添加属性的getter
*/
- (AFImageDownloadReceipt *)af_imageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_imageDownloadReceiptKeyForState(state));
}
/**
通过Runtime的关联对象为分类添加属性的setter
*/
- (void)af_setImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_imageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
复制代码
2.1.2.backgroundImage相关
- 静态字符
// 普通
static char AFBackgroundImageDownloadReceiptNormal;
// 高亮
static char AFBackgroundImageDownloadReceiptHighlighted;
// 已选
static char AFBackgroundImageDownloadReceiptSelected;
// 禁用
static char AFBackgroundImageDownloadReceiptDisabled;
复制代码
- 静态方法
/**
这个方法通过传入的控件状态返回对应的静态字符
*/
static const char * af_backgroundImageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFBackgroundImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFBackgroundImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFBackgroundImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFBackgroundImageDownloadReceiptNormal;
}
}
复制代码
- 属性的访问方法
/**
通过Runtime的关联对象为分类添加属性的getter
*/
- (AFImageDownloadReceipt *)af_backgroundImageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state));
}
/**
通过Runtime的关联对象为分类添加属性的setter
*/
- (void)af_setBackgroundImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
复制代码
2.2.方法实现
- 属性的访问方法
/**
通过Runtime的关联对象为分类添加sharedImageDownloader属性的getter
*/
+ (AFImageDownloader *)sharedImageDownloader {
return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
}
/**
通过Runtime的关联对象为分类添加sharedImageDownloader属性的setter
*/
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
复制代码
- image相关接口方法实现
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
{
// 调用下面的方法
[self setImageForState:state withURL:url placeholderImage:nil];
}
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
// 调用下面的方法
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
// 如果这个网络请求正在进行中就不重复执行了
if ([self isActiveTaskURLEqualToURLRequest:urlRequest forState:state]) {
return;
}
// 取消掉这个状态的图片下载任务
[self cancelImageDownloadTaskForState:state];
// 实例化图片下载器
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
// 获取到图片缓存对象
id <AFImageRequestCache> imageCache = downloader.imageCache;
// 获取到缓存图片
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
// 如果有缓存
if (cachedImage) {
// 如果设置了成功回调block就调用,否则就为按钮设置图片
if (success) {
success(urlRequest, nil, cachedImage);
} else {
[self setImage:cachedImage forState:state];
}
[self af_setImageDownloadReceipt:nil forState:state];
// 如果没有缓存
} else {
// 如果有占位图先设置占位图
if (placeholderImage) {
[self setImage:placeholderImage forState:state];
}
// 开始下载
__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_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
// 如果设置了成功回调block就调用,否则就为按钮设置图片
if (success) {
success(request, response, responseObject);
} else if(responseObject) {
[strongSelf setImage:responseObject forState:state];
}
// 将保存下载封装对象的属性置nil
[strongSelf af_setImageDownloadReceipt:nil forState:state];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
// 如果是当前按钮的下载回调
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[strongSelf af_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
// 失败回调block
if (failure) {
failure(request, response, error);
}
// 将保存下载封装对象的属性置nil
[strongSelf af_setImageDownloadReceipt:nil forState:state];
}
}];
// 用分类的属性保存图片下载封装对象
[self af_setImageDownloadReceipt:receipt forState:state];
}
}
- (void)cancelImageDownloadTaskForState:(UIControlState)state {
// 通过状态获取到图片下载封装对象
AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state];
if (receipt != nil) {
// 根据获取到的图片下载封装对象取消对应的图片下载任务
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:receipt];
// 把保存图片下载封装对象的属性置nil
[self af_setImageDownloadReceipt:nil forState:state];
}
}
复制代码
- backgroundImage相关接口方法实现
backgroundImage的实现和image的实现除了在设置图片时一个给backgroundImage,一个给image之外都相同。
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
{
[self setBackgroundImageForState:state withURL:url placeholderImage:nil];
}
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setBackgroundImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setBackgroundImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
if ([self isActiveBackgroundTaskURLEqualToURLRequest:urlRequest forState:state]) {
return;
}
[self cancelBackgroundImageDownloadTaskForState:state];
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) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
[self setBackgroundImage:cachedImage forState:state];
}
[self af_setBackgroundImageDownloadReceipt:nil forState:state];
} else {
if (placeholderImage) {
[self setBackgroundImage:placeholderImage forState:state];
}
__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_backgroundImageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
if (success) {
success(request, response, responseObject);
} else if(responseObject) {
[strongSelf setBackgroundImage:responseObject forState:state];
}
[strongSelf af_setBackgroundImageDownloadReceipt:nil forState:state];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[strongSelf af_backgroundImageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
[strongSelf af_setBackgroundImageDownloadReceipt:nil forState:state];
}
}];
[self af_setBackgroundImageDownloadReceipt:receipt forState:state];
}
}
- (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state {
AFImageDownloadReceipt *receipt = [self af_backgroundImageDownloadReceiptForState:state];
if (receipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:receipt];
[self af_setBackgroundImageDownloadReceipt:nil forState:state];
}
}
复制代码
- 私有方法
/**
判断图片下载请求是否已经正在进行中
*/
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest forState:(UIControlState)state {
// 先根据状态获取到属性中保存的图片下载封装对象
AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state];
// 根据两者的链接是否相同进行比较
return [receipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
/**
判断背景图片下载请求是否已经正在进行中
*/
- (BOOL)isActiveBackgroundTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest forState:(UIControlState)state {
// 实现和上面的方法相同
AFImageDownloadReceipt *receipt = [self af_backgroundImageDownloadReceiptForState:state];
return [receipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
复制代码
3.总结
当我们利用这个分类为按钮设置图片时
- 首先会根据要设置的控件状态和图片链接判断是否是重复下载,如果是重复下载就不继续向下进行了。
- 接着会取消在设置的控件状态下正在进行的下载操作(如果有的话),也就是说只有最新的这次图片设置才有效。
- 然后获取到图片下载器的图片缓存对象,从图片缓存对象中查找是否有缓存,如果有缓存的话会直接取出添加到按钮上,但是如果用户设置了成功回调block,就不会将图片添加到按钮上,而是会调用blokc,将结果传递给用户,让用户来处理。
- 如果没有缓存,就会先判断有没有占位图,如果有占位图的话,就先给按钮设置占位图。
- 接下来将UUID作为标识符开启图片下载任务,并将任务封装对象保存在属性中,属性的命名是根据图片要设置的控件状态相关联的。
- 下载任务执行完成后,无论成功还是失败,都会先根据设置的控件状态获取到对应的下载任务封装对象,对比下载任务封装对象的标识符和UUID判断是否是同一任务的回调,只用是同一任务的回调才继续执行
- 在下载成功的情况下,如果用户设置了成功回调block,就调用block,将结果传递给用户,让用户来处理,如果没有设置,就直接将下载好的图片添加到按钮的指定状态上,并将指定状态所对应的保存下载任务封装对象的属性置nil。
- 当下载失败时,如果用户设置了失败回调block,就调用block,将结果传递给用户,让用户来处理,如果没有设置,就将指定状态所对应的保存下载任务封装对象的属性置nil。
源码阅读系列:AFNetworking
源码阅读:AFNetworking(二)——AFURLRequestSerialization
源码阅读:AFNetworking(三)——AFURLResponseSerialization
源码阅读:AFNetworking(四)——AFSecurityPolicy
源码阅读:AFNetworking(五)——AFNetworkReachabilityManager
源码阅读:AFNetworking(六)——AFURLSessionManager
源码阅读:AFNetworking(七)——AFHTTPSessionManager
源码阅读:AFNetworking(八)——AFAutoPurgingImageCache
源码阅读:AFNetworking(九)——AFImageDownloader
源码阅读:AFNetworking(十)——AFNetworkActivityIndicatorManager
源码阅读:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking
源码阅读:AFNetworking(十二)——UIButton+AFNetworking
源码阅读:AFNetworking(十三)——UIImageView+AFNetworking
源码阅读:AFNetworking(十四)——UIProgressView+AFNetworking