SDWebImage源码解读之分类

作者: 老马的春天 | 来源:发表于2017-01-23 14:21 被阅读411次

    第十一篇

    前言

    我们知道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的源码就看到这里了,下一篇我会把这十一篇中用到的知识点进行一个总结。

    由于个人知识有限,如有错误之处,还望各路大侠给予指出啊

    1. SDWebImage源码解读 之 NSData+ImageContentType 简书 博客园
    2. SDWebImage源码解读 之 UIImage+GIF 简书 博客园
    3. SDWebImage源码解读 之 SDWebImageCompat 简书 博客园
    4. SDWebImage源码解读 之SDWebImageDecoder 简书 博客园
    5. SDWebImage源码解读 之SDWebImageCache(上) 简书 博客园
    6. SDWebImage源码解读之SDWebImageCache(下) 简书 博客园
    7. SDWebImage源码解读之SDWebImageDownloaderOperation 简书 博客园
    8. SDWebImage源码解读之SDWebImageDownloader 简书 博客园
    9. SDWebImage源码解读之SDWebImageManager 简书 博客园
    10. SDWebImage源码解读之SDWebImagePrefetcher 简书 博客园

    相关文章

      网友评论

        本文标题:SDWebImage源码解读之分类

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