源碼閱讀:AFNetworking(十二)——UIButton+AFNetworking

該文章閱讀的AFNetworking的版本爲3.2.0。緩存

這個分類是爲UIButton添加異步加載網絡圖片的方法bash

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。

源碼閱讀系列:AFNetworkingpost

源碼閱讀:AFNetworking(一)——從使用入手ui

源碼閱讀:AFNetworking(二)——AFURLRequestSerializationurl

源碼閱讀:AFNetworking(三)——AFURLResponseSerializationspa

源碼閱讀:AFNetworking(四)——AFSecurityPolicy3d

源碼閱讀:AFNetworking(五)——AFNetworkReachabilityManagercode

源碼閱讀: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

相關文章
相關標籤/搜索