前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AFNetworking源码探究(二十一) —— UIKit相关之UIImageView+AFNetworking分类

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

原创
作者头像
conanma
修改2021-09-03 18:02:23
5790
修改2021-09-03 18:02:23
举报
文章被收录于专栏:正则正则

回顾

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


AFNetworking类

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

代码语言:javascript
复制
#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进行绑定获取分类的对象。

代码语言:javascript
复制
+ (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获取下载器的实例化对象。


下载接口的调用

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

代码语言:javascript
复制
- (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非常类似,实现也差不多这样。


下载接口的实现

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

代码语言:javascript
复制
- (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. 容错处理

代码语言:javascript
复制
if ([urlRequest URL] == nil) {
    [self cancelImageDownloadTask];
    self.image = placeholderImage;
    return;
}

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

代码语言:javascript
复制
- (void)cancelImageDownloadTask {
    if (self.af_activeImageDownloadReceipt != nil) {
        [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
        [self clearActiveDownloadInformation];
     }
}

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

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

2. 存在任务的判断

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

代码语言:javascript
复制
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
    return;
}

[self cancelImageDownloadTask];
代码语言:javascript
复制
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest {
    return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}

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

首先看一下这段代码

代码语言:javascript
复制
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) 获取缓存图像

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

代码语言:javascript
复制
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) 缓存图像存在

主要对应下面这段代码

代码语言:javascript
复制
if (cachedImage) {
    if (success) {
        success(urlRequest, nil, cachedImage);
    } else {
        self.image = cachedImage;
    }
    [self clearActiveDownloadInformation];
} 

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

(c) 缓存图像不存在

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

代码语言:javascript
复制
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的分类,用于下载图像。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 回顾
  • AFNetworking类
  • runtime绑定
  • 下载接口的调用
  • 下载接口的实现
    • 1. 容错处理
      • 2. 存在任务的判断
        • 3. 获取缓存图像并做决策
        • 后记
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档