zoukankan      html  css  js  c++  java
  • SDWebImage源码解读之分类

    第十一篇

    前言

    我们知道SDWebImageManager是用来管理图片下载的,但我们平时的开发更多的是使用UIImageViewUIButton这两个控件显示图片。

    按照正常的想法,我们只需要在他们的分类中,通过SDWebImageManager把图片下载下载之后,再进行赋值就行了。但这样的设计并不是最好的设计,我们在准备提供一项功能的时候,应该要尽可能的弄明白这个功能的使用者是谁?这些使用者的共同点是什么?

    UIImageViewUIButton这两个控件都继承自UIView。那么我们是不是可以给UIView赋予加载图片的功能,然后,
    UIImageViewUIButton不就都有这个能力了吗?

    除了讲解这些之外,在本文的最后,我们给view的layer也添加这个能力。

    UIView+WebCache

    /**
     * Set the imageView `image` with an `url` and optionally a placeholder image.
     *
     * The download is asynchronous and cached.
     *
     * @param url            The url for the image.
     * @param placeholder    The image to be set initially, until the image request finishes.
     * @param options        The options to use when downloading the image. @see SDWebImageOptions for the possible values.
     * @param operationKey   A string to be used as the operation key. If nil, will use the class name
     * @param setImageBlock  Block used for custom set image code
     * @param progressBlock  A block called while image is downloading
     *                       @note the progress block is executed on a background queue
     * @param completedBlock A block called when operation has been completed. This block has no return value
     *                       and takes the requested UIImage as first parameter. In case of error the image parameter
     *                       is nil and the second parameter may contain an NSError. The third parameter is a Boolean
     *                       indicating if the image was retrieved from the local cache or from the network.
     *                       The fourth parameter is the original image url.
     */
    - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                      placeholderImage:(nullable UIImage *)placeholder
                               options:(SDWebImageOptions)options
                          operationKey:(nullable NSString *)operationKey
                         setImageBlock:(nullable SDSetImageBlock)setImageBlock
                              progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                             completed:(nullable SDExternalCompletionBlock)completedBlock;
                             

    实现方法:

    - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                      placeholderImage:(nullable UIImage *)placeholder
                               options:(SDWebImageOptions)options
                          operationKey:(nullable NSString *)operationKey
                         setImageBlock:(nullable SDSetImageBlock)setImageBlock
                              progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                             completed:(nullable SDExternalCompletionBlock)completedBlock {
        /// 每一个view都给他绑定一个operation,通过一个key来获取这个operation
        /// 如果operationKey为nil,就采用他自身的类的字符串作为key
        NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
        /// 这是一个view的分类方法,目的是取消之前绑定的operation
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
        /// 为自身绑定一个URL
        objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        /// 如果不是延迟显示placeholder的情况
        if (!(options & SDWebImageDelayPlaceholder)) {
            dispatch_main_async_safe(^{
                /// 在图片下载下来之前,先给自身赋值一个placeholder
                [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
            });
        }
        
        if (url) {
            // check if activityView is enabled or not
            /// 如果设置了显示加载动画,就添加ActivityIndicatorView
            if ([self sd_showActivityIndicatorView]) {
                [self sd_addActivityIndicator];
            }
            
            /// 开始下载图片
            __weak __typeof(self)wself = self;
            id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                __strong __typeof (wself) sself = wself;
                /// 下载完成后关闭动画
                [sself sd_removeActivityIndicator];
                if (!sself) {
                    return;
                }
                dispatch_main_async_safe(^{
                    if (!sself) {
                        return;
                    }
                    if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                        completedBlock(image, error, cacheType, url);
                        return;
                    } else if (image) {
                        [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    } else {
                        /// 如果是设置了SDWebImageDelayPlaceholder,那么就会在下载完成之后给自身赋值placeholder
                        if ((options & SDWebImageDelayPlaceholder)) {
                            [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                            [sself sd_setNeedsLayout];
                        }
                    }
                    if (completedBlock && finished) {
                        completedBlock(image, error, cacheType, url);
                    }
                });
            }];
            /// 绑定operation
            [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        } else {
            dispatch_main_async_safe(^{
                [self sd_removeActivityIndicator];
                if (completedBlock) {
                    NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                    completedBlock(nil, error, SDImageCacheTypeNone, url);
                }
            });
        }
    }

    其实这个核心的实现方法也很简单,逻辑也不复杂,需要注意一下下边的方法:

    - (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {
        if (setImageBlock) {
            setImageBlock(image, imageData);
            return;
        }
        
    #if SD_UIKIT || SD_MAC
        if ([self isKindOfClass:[UIImageView class]]) {
            UIImageView *imageView = (UIImageView *)self;
            imageView.image = image;
        }
    #endif
        
    #if SD_UIKIT
        if ([self isKindOfClass:[UIButton class]]) {
            UIButton *button = (UIButton *)self;
            [button setImage:image forState:UIControlStateNormal];
        }
    #endif
    }

    这个方法说明如果我们设置了setImageBlock,那么view的显示图片的逻辑应该写到这个Block中,如果setImageBlock为nil,就判断是不是UIImageViewUIButton

    UIImageView的实现

    - (void)sd_setImageWithURL:(nullable NSURL *)url
              placeholderImage:(nullable UIImage *)placeholder
                       options:(SDWebImageOptions)options
                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                     completed:(nullable SDExternalCompletionBlock)completedBlock {
        [self sd_internalSetImageWithURL:url
                        placeholderImage:placeholder
                                 options:options
                            operationKey:nil
                           setImageBlock:nil
                                progress:progressBlock
                               completed:completedBlock];
    }

    CALay的实现

    - (void)sd_setImageWithURL:(nullable NSURL *)url
              placeholderImage:(nullable UIImage *)placeholder
                       options:(SDWebImageOptions)options
                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                     completed:(nullable SDExternalCompletionBlock)completedBlock {
        NSString *validOperationKey = @"CALayerImages" ?: NSStringFromClass([self class]);
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
        objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        if (!(options & SDWebImageDelayPlaceholder)) {
            dispatch_main_async_safe(^{
                self.contents = (__bridge id _Nullable)placeholder.CGImage;
            });
        }
        
        if (url) {
            __weak __typeof(self)wself = self;
            id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                __strong __typeof (wself) sself = wself;
    
                if (!sself) {
                    return;
                }
                dispatch_main_async_safe(^{
                    if (!sself) {
                        return;
                    }
                    if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                        completedBlock(image, error, cacheType, url);
                        return;
                    } else if (image) {
                        self.contents = (__bridge id _Nullable)image.CGImage;
                       
                    } else {
                        if ((options & SDWebImageDelayPlaceholder)) {
                            self.contents = (__bridge id _Nullable)placeholder.CGImage;
                        }
                    }
                    if (completedBlock && finished) {
                        completedBlock(image, error, cacheType, url);
                    }
                });
            }];
            [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        } else {
            dispatch_main_async_safe(^{
       
                if (completedBlock) {
                    NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                    completedBlock(nil, error, SDImageCacheTypeNone, url);
                }
            });
        }
    }
    
    
    
    
    - (SDOperationsDictionary *)operationDictionary {
        SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
        if (operations) {
            return operations;
        }
        operations = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        return operations;
    }
    
    - (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
        if (key) {
            [self sd_cancelImageLoadOperationWithKey:key];
            if (operation) {
                SDOperationsDictionary *operationDictionary = [self operationDictionary];
                operationDictionary[key] = operation;
            }
        }
    }
    
    - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
        // Cancel in progress downloader from queue
        SDOperationsDictionary *operationDictionary = [self operationDictionary];
        id operations = operationDictionary[key];
        if (operations) {
            if ([operations isKindOfClass:[NSArray class]]) {
                for (id <SDWebImageOperation> operation in operations) {
                    if (operation) {
                        [operation cancel];
                    }
                }
            } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
                [(id<SDWebImageOperation>) operations cancel];
            }
            [operationDictionary removeObjectForKey:key];
        }
    }
    
    - (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
        if (key) {
            SDOperationsDictionary *operationDictionary = [self operationDictionary];
            [operationDictionary removeObjectForKey:key];
        }
    }

    实现思路跟给view添加该能力完全相同。

     [self.imageView.layer sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"4-3397163ecdb3855a0a4139c34a695885.jpg"] options:0 progress:nil completed:nil];

    总结

    SDWebImage的源码就看到这里了,下一篇我会把这十一篇中用到的知识点进行一个总结。

  • 相关阅读:
    Android 架构:Android Jetpack 架构组件的学习和分析
    Android 看源码学 Binder
    Android Okhttp 源码分析(待完成)
    Android Glide 源码分析系列(待完成)
    界面2
    使用spring 4.0 + maven 构建超简单的web项目
    maven中跳过单元测试
    Hibernate+maven+eclipse 实现自动建表
    android开发学习---开发一个简易的短信发送器
    java面试题--实现一个百亿的计算器
  • 原文地址:https://www.cnblogs.com/zhengxingpeng/p/6679229.html
Copyright © 2011-2022 走看看