源码阅读:AFNetworking(十二)——UIButton+AFNetworking

该文章阅读的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(一)——从使用入手

源码阅读: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

源码阅读:AFNetworking(十五)——UIRefreshControl+AFNetworking

源码阅读:AFNetworking(十六)——UIWebView+AFNetworking

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值