该文章阅读的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]; // 获取到图片缓存对象 idimageCache = 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]; idimageCache = 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