美文网首页AFNetworking源码探究
AFNetworking源码探究(二十二) —— UIKit相关

AFNetworking源码探究(二十二) —— UIKit相关

作者: 刀客传奇 | 来源:发表于2018-03-05 09:17 被阅读83次

    版本记录

    版本号 时间
    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图像下载(三)
    21. AFNetworking源码探究(二十一) —— UIKit相关之UIImageView+AFNetworking分类(四)

    回顾

    上一篇讲述了关于UIImageView的分类,用于下载图像。这一篇讲述关于UIButton+AFNetworking的UIButton的一个分类。其实看过上一篇的话,这一篇都是类似的,大家简单看一下就可以了。


    接口

    我们先看一下API接口

    #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 `UIButton` class. The methods in this category provide support for loading remote images and background images asynchronously from a URL.
    
     @warning Compound values for control `state` (such as `UIControlStateHighlighted | UIControlStateDisabled`) are unsupported.
     */
    @interface UIButton (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 as the image for the specified state 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.
    
     @param state The control state.
     @param url The URL used for the image request.
     */
    - (void)setImageForState:(UIControlState)state
                     withURL:(NSURL *)url;
    
    /**
     Asynchronously downloads an image from the specified URL, and sets it as the image for the specified state 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.
    
     @param state The control state.
     @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 button will not change its image until the image request finishes.
     */
    - (void)setImageForState:(UIControlState)state
                     withURL:(NSURL *)url
            placeholderImage:(nullable UIImage *)placeholderImage;
    
    /**
     Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state 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 button before returning. If no success block is specified, the default behavior of setting the image with `setImage:forState:` is applied.
    
     @param state The control state.
     @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 button 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)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;
    
    
    ///-------------------------------
    /// @name Setting Background Image
    ///-------------------------------
    
    /**
     Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state once the request is finished. Any previous background image request for the receiver will be cancelled.
    
     If the background image is cached locally, the background image is set immediately, otherwise the specified placeholder background image will be set immediately, and then the remote background image will be set once the request is finished.
    
     @param state The control state.
     @param url The URL used for the background image request.
     */
    - (void)setBackgroundImageForState:(UIControlState)state
                               withURL:(NSURL *)url;
    
    /**
     Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state 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.
    
     @param state The control state.
     @param url The URL used for the background image request.
     @param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes.
     */
    - (void)setBackgroundImageForState:(UIControlState)state
                               withURL:(NSURL *)url
                      placeholderImage:(nullable UIImage *)placeholderImage;
    
    /**
     Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state 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 button before returning. If no success block is specified, the default behavior of setting the image with `setBackgroundImage:forState:` is applied.
    
     @param state The control state.
     @param urlRequest The URL request used for the image request.
     @param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background 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)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;
    
    
    ///------------------------------
    /// @name Canceling Image Loading
    ///------------------------------
    
    /**
     Cancels any executing image task for the specified control state of the receiver, if one exists.
    
     @param state The control state.
     */
    - (void)cancelImageDownloadTaskForState:(UIControlState)state;
    
    /**
     Cancels any executing background image task for the specified control state of the receiver, if one exists.
    
     @param state The control state.
     */
    - (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #endif
    

    其实这个接口主要包含四个模块:

    • 获取图像下载器
    • 下载图像
    • 下载背景图
    • 取消下载图像(包括图像和背景图)

    获取图像下载器

    下面我们就看一下获取图像下载器

    /**
     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;
    

    图像下载器是用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);
    }
    

    下载图像

    下面看一下下载图像的实现。

    - (void)setImageForState:(UIControlState)state
                     withURL:(NSURL *)url;
    - (void)setImageForState:(UIControlState)state
                     withURL:(NSURL *)url
            placeholderImage:(nullable UIImage *)placeholderImage;
    - (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)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;
    
        //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 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]) {
                               if (success) {
                                   success(request, response, responseObject);
                               } else if(responseObject) {
                                   [strongSelf setImage:responseObject forState:state];
                               }
                               [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]) {
                               if (failure) {
                                   failure(request, response, error);
                               }
                               [strongSelf  af_setImageDownloadReceipt:nil forState:state];
                           }
                       }];
    
            [self af_setImageDownloadReceipt:receipt forState:state];
        }
    }
    

    这里和AFN中UIImageView以及SDWebImage中的调用形式都是一样的。这个和AFN中UIImageView的实现是类似的,也不多说了。

    主要就是首先判断是否下载是否存在,如果存在,那么就return,取消下载。然后获取下载器和缓存,获取缓存中的图像,如果缓存图像存在,就进行回调,如果缓存图像不存在就开始进行下载。


    下载背景图像

    看一下调用接口

    - (void)setBackgroundImageForState:(UIControlState)state
                               withURL:(NSURL *)url;
    - (void)setBackgroundImageForState:(UIControlState)state
                               withURL:(NSURL *)url
                      placeholderImage:(nullable UIImage *)placeholderImage;
    - (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)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];
        }
    }
    

    这个实现原理就不都说了,和下载图像中是类似的。

    后记

    本篇讲述了UIButton+AFNetworking的UIButton的一个分类。分析了其下载器的下载、图像的下载以及背景图像的下载。

    相关文章

      网友评论

        本文标题:AFNetworking源码探究(二十二) —— UIKit相关

        本文链接:https://www.haomeiwen.com/subject/xkoxfftx.html