AFNetworking源码探究(二十一) —— UIKit相关之UIImageView+AFNetworking分类(四)

版本记录

版本号 时间
V1.0 2018.03.05

前言

我们做APP发起网络请求,都离不开一个非常有用的框架AFNetworking,可以说这个框架的知名度已经超过了苹果的底层网络请求部分,很多人可能不知道苹果底层是如何发起网络请求的,但是一定知道AFNetworking,接下来几篇我们就一起详细的解析一下这个框架。感兴趣的可以看上面写的几篇。
1. AFNetworking源码探究(一) —— 基本介绍
2. AFNetworking源码探究(二) —— GET请求实现之NSURLSessionDataTask实例化(一)
3. AFNetworking源码探究(三) —— GET请求实现之任务进度设置和通知监听(一)
4. AFNetworking源码探究(四) —— GET请求实现之代理转发思想(一)
5. AFNetworking源码探究(五) —— AFURLSessionManager中NSURLSessionDelegate详细解析(一)
6. AFNetworking源码探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate详细解析(一)
7. AFNetworking源码探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate详细解析(一)
8. AFNetworking源码探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate详细解析(一)
9. AFNetworking源码探究(九) —— AFURLSessionManagerTaskDelegate中三个转发代理方法详细解析(一)
10. AFNetworking源码探究(十) —— 数据解析之数据解析架构的分析(一)
11. AFNetworking源码探究(十一) —— 数据解析之子类中协议方法的实现(二)
12. AFNetworking源码探究(十二) —— 数据解析之子类中协议方法的实现(三)
13. AFNetworking源码探究(十三) —— AFSecurityPolicy与安全认证 (一)
14. AFNetworking源码探究(十四) —— AFSecurityPolicy与安全认证 (二)
15. AFNetworking源码探究(十五) —— 请求序列化之架构分析(一)
16. AFNetworking源码探究(十六) —— 请求序列化之协议方法的实现(二)
17. AFNetworking源码探究(十七) —— _AFURLSessionTaskSwizzling实现方法交换(转载)(一)
18. AFNetworking源码探究(十八) —— UIKit相关之AFNetworkActivityIndicatorManager(一)
19. AFNetworking源码探究(十九) —— UIKit相关之几个分类(二)
20. AFNetworking源码探究(二十) —— UIKit相关之AFImageDownloader图像下载(三)

回顾

上一篇是关于AFImageDownloader图像下载的内容,这一篇主要是关于UIImageView的分类AFNetworking


AFNetworking类

先看一下UIImageView的分类AFNetworking的接口。

#import <Foundation/Foundation.h>

#import <TargetConditionals.h>

#if TARGET_OS_IOS || TARGET_OS_TV

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@class AFImageDownloader;

/**
 This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL.
 */
// 此类为UIKit框架的UIImageView类添加方法。 此类别中的方法支持从URL异步加载远程图像。
@interface UIImageView (AFNetworking)

///------------------------------------
/// @name Accessing the Image Downloader
///------------------------------------

/**
 Set the shared image downloader used to download images.

 @param imageDownloader The shared image downloader used to download images.
 */
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;

/**
 The shared image downloader used to download images.
 */
+ (AFImageDownloader *)sharedImageDownloader;

///--------------------
/// @name Setting Image
///--------------------

/**
 Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.

 If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.

 By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`

 @param url The URL used for the image request.
 */
// 异步地从指定的URL下载图像,并在请求完成后进行设置。 
// 以前任何接收者的图像请求都将被取消。

// 如果图像在本地缓存,则立即设置图像,否则将立即设置指定的占位符图像,
// 然后在请求完成后设置远程图像。

// 默认情况下,URL请求的“Accept”标头字段值为“image / *”,缓存策略为“NSURLCacheStorageAllowed”,
// 超时间隔为30秒,并且设置为不处理cookie。 要以不同的方式配置URL请求,
// 请使用`setImageWithURLRequest:placeholderImage:success:failure:`
- (void)setImageWithURL:(NSURL *)url;

/**
 Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.

 If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.

 By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`

 @param url The URL used for the image request.
 @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
 */
- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(nullable UIImage *)placeholderImage;

/**
 Asynchronously downloads an image from the specified URL request, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.

 If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.

 If a success block is specified, it is the responsibility of the block to set the image of the image view before returning. If no success block is specified, the default behavior of setting the image with `self.image = image` is applied.

 @param urlRequest The URL request used for the image request.
 @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
 @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
 @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
 */
- (void)setImageWithURLRequest:(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;

/**
 Cancels any executing image operation for the receiver, if one exists.
 */
- (void)cancelImageDownloadTask;

@end

NS_ASSUME_NONNULL_END

#endif

怎么样,大家有没有很熟悉,没错,这个和SDWebImage非常类似,包括接口的调用和实现。


runtime绑定

这里采用runtime进行绑定获取分类的对象。

+ (AFImageDownloader *)sharedImageDownloader {
    return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
}

+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
    objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

然后调用AFImageDownloader获取下载器的实例化对象。


下载接口的调用

下面看一下下载图像的接口调用。

- (void)setImageWithURL:(NSURL *)url;
- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(nullable UIImage *)placeholderImage;
- (void)setImageWithURLRequest:(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;

这个接口和SDWebImage非常类似,实现也差不多这样。


下载接口的实现

下面就看一下下载接口的实现。

- (void)setImageWithURL:(NSURL *)url {
    [self setImageWithURL:url placeholderImage:nil];
}

- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(UIImage *)placeholderImage
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];

    [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}

- (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
{

    if ([urlRequest URL] == nil) {
        [self cancelImageDownloadTask];
        self.image = placeholderImage;
        return;
    }

    if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
        return;
    }

    [self cancelImageDownloadTask];

    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.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;
    }
}

这个类主要做下面几个工作:

1. 容错处理

if ([urlRequest URL] == nil) {
    [self cancelImageDownloadTask];
    self.image = placeholderImage;
    return;
}

这里如果request的URL不存在的话,那就无法请求了,这里就将当前UIImageView的image设置为palceHolder图像,并取消该图像下载任务。

- (void)cancelImageDownloadTask {
    if (self.af_activeImageDownloadReceipt != nil) {
        [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
        [self clearActiveDownloadInformation];
     }
}

这里就是图像下载任务取消的实现:

  • 首先就是判断下载任务的凭据是否存在,如果不存在不用管,说明没有这个任务,这里只处理有这个任务的情况。
  • 调用下载器的cancelTaskForImageDownloadReceipt:方法,带人凭据参数,取消下载任务。
  • 初始化凭据参数,置为nil,实现过程如下。
- (void)clearActiveDownloadInformation {
    self.af_activeImageDownloadReceipt = nil;
}

2. 存在任务的判断

下面就看一下根据URL判断任务是否存在,如果存在就return,接着就是取消图像下载任务。

if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
    return;
}

[self cancelImageDownloadTask];
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest {
    return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}

3. 获取缓存图像并做决策

首先看一下这段代码

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.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;
}

这段代码,主要完成下面几个工作:

  • 获取缓存图像
  • 如果存在,就进行回调和设置图像
  • 不存在就开始下载图像

(a) 获取缓存图像

我们首先看一下缓存图像。

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];

这里,首先是获取下载器,然后获取下载器的缓存,最后根据请求request获取UIImage。

(b) 缓存图像存在

主要对应下面这段代码

if (cachedImage) {
    if (success) {
        success(urlRequest, nil, cachedImage);
    } else {
        self.image = cachedImage;
    }
    [self clearActiveDownloadInformation];
} 

如果缓存图像存在,如果success的block存在就回调出去,否则就赋值给image。最后还是调用clearActiveDownloadInformation,清除下载信息。

(c) 缓存图像不存在

其实主要对应下面这段代码

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;
}

缓存图像不存在,那就是要开始下载了。主要步骤如下:

  • 暂时将图像设置为占位符。
  • 根据下载器返回的凭据,更新内存中的有效凭据self.af_activeImageDownloadReceipt
  • 用下载器进行下载,不管成功还是失败都进行相应的回调,并清除下载信息clearActiveDownloadInformation。并在成功的时候设置图像替换掉下载图strongSelf.image = responseObject

后记

本篇讲述了关于UIImageView的分类,用于下载图像。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容