美文网首页
SDWebImage源码学习(三) --- WebCache C

SDWebImage源码学习(三) --- WebCache C

作者: Maj_sunshine | 来源:发表于2018-04-20 16:56 被阅读30次
    UIButton+WebCache
    UIImageView+HighlightedWebCache
    UIImageView+WebCache

    在源码中这些UIView子类的分类都会最终调用UIView+WebCache 中的全能初始化来进行设置图片。我看了一下UIButton+WebCache,理解为这样。

     // 保存各类型图片各个状态state的key 
    static inline NSString * imageURLKeyForState(UIControlState state) {
        return [NSString stringWithFormat:@"image_%lu", (unsigned long)state];
    }
    
    static inline NSString * backgroundImageURLKeyForState(UIControlState state) {
        return [NSString stringWithFormat:@"backgroundImage_%lu", (unsigned long)state];
    }
    
    static inline NSString * imageOperationKeyForState(UIControlState state) {
        return [NSString stringWithFormat:@"UIButtonImageOperation%lu", (unsigned long)state];
    }
    
    static inline NSString * backgroundImageOperationKeyForState(UIControlState state) {
        return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state];
    }
    
    #pragma mark - Image
     // 获取当前正常图片的url
    - (nullable NSURL *)sd_currentImageURL {
         // 从sd_imageURLStorage字典 中获取当前state为key对应的url
        NSURL *url = self.sd_imageURLStorage[imageURLKeyForState(self.state)];
    
        if (!url) {
             // 没有,则获取state == UIControlStateNormal 时的url
            url = self.sd_imageURLStorage[imageURLKeyForState(UIControlStateNormal)];
        }
    
        return url;
    }
    
     // 下列有七个接口,全能的初始化方法为最后一个,开发中自定义视图的接口设计尽量参照全能初始化来创建。
    - (nullable NSURL *)sd_imageURLForState:(UIControlState)state {
        return self.sd_imageURLStorage[imageURLKeyForState(state)];
    }
    
    - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state {
        [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
    }
    
    - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder {
        [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
    }
    
    - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {
        [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
    }
    
    - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state completed:(nullable SDExternalCompletionBlock)completedBlock {
        [self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
    }
    
    - (void)sd_setImageWithURL:(nullable NSURL *)url forState:(UIControlState)state placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {
        [self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
    }
    
     // 全能初始化方法
    - (void)sd_setImageWithURL:(nullable NSURL *)url
                      forState:(UIControlState)state
              placeholderImage:(nullable UIImage *)placeholder
                       options:(SDWebImageOptions)options
                     completed:(nullable SDExternalCompletionBlock)completedBlock {
         // 设置图片url
        if (!url) {
            [self.sd_imageURLStorage removeObjectForKey:imageURLKeyForState(state)];
        } else {
            self.sd_imageURLStorage[imageURLKeyForState(state)] = url;
        }
        
         // 调用UIView+WebCache 中的全能初始化方法设置图片
        __weak typeof(self)weakSelf = self;
        [self sd_internalSetImageWithURL:url
                        placeholderImage:placeholder
                                 options:options
                            operationKey:imageOperationKeyForState(state)
                           setImageBlock:^(UIImage *image, NSData *imageData) {
                               [weakSelf setImage:image forState:state];
                           }
                                progress:nil
                               completed:completedBlock];
    }
    
    #pragma mark - Cancel
     // 取消当前图片的下载
    - (void)sd_cancelImageLoadForState:(UIControlState)state {
        [self sd_cancelImageLoadOperationWithKey:imageOperationKeyForState(state)];
    }
    
    - (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state {
        [self sd_cancelImageLoadOperationWithKey:backgroundImageOperationKeyForState(state)];
    }
    
    #pragma mark - Private
     // 给UIButton关联一个字典dic,保存image的url,key为图片设置的state
    - (SDStateImageURLDictionary *)sd_imageURLStorage {
        SDStateImageURLDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
        if (!storage) {
             // 如果为空,调用get
            storage = [NSMutableDictionary dictionary];
            objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    
        return storage;
    }
    

    UIView+WebCache

    所有UIView以及子类都通过下面这个方法加载图片

    /**
     <#Description#>
     
     @param url 图片的网址
     @param placeholder 占位图,图像获取到后替换占位图
     @param options 图片加载选项 是个位移枚举
     @param operationKey 用作操作键的字符串。如果为空,将使用类名.主要使用来取消一个 operation
     @param setImageBlock 设置图片回调
     @param progressBlock 进度回调
     @param completedBlock 图片加载完成回调
     @param context <#context description#>
     */
    - (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
                               context:(nullable NSDictionary<NSString *, id> *)context {
         // key为当前UIView类的名称
        NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
         // 先取消当前类的其他Operation对象
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
         // 添加一个url属性
        objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
         // 如果没有options 或者 不是延时显示占位符
        if (!(options & SDWebImageDelayPlaceholder)) {
             // 这个是用于FLAnimatedImageView+WebCache,FLAnimatedImageView是由Flipboard开源的iOS平台上播放GIF动画的一个优秀解决方案,在内存占用和播放体验都有不错的表现。这里主要用SDWebImage的缓存机制,制作一个简易使用的类别。
            if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
                dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
                dispatch_group_enter(group);
            }
             // 设置占位图
            dispatch_main_async_safe(^{
                [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
            });
        }
        
         // 如果有url
        if (url) {
            // 获取图片(先从内存找,若无再从磁盘找,若仍无则从网络下载。)
            // 先从内存找,若找到,则在主线程设置图片,并调用block回调;若在磁盘找到,则还要将其添加到内存缓存再做后续的;若是从网络下载而得,则内存和磁盘均要添加缓存,而后再进行后续的事。
            
            // 如果需要显示菊花转
            if ([self sd_showActivityIndicatorView]) {
                 // 添加菊花
                [self sd_addActivityIndicator];
            }
            
            // 重置进度的总单元数
            self.sd_imageProgress.totalUnitCount = 0;
             // 当前完成的单元数
            self.sd_imageProgress.completedUnitCount = 0;
            
            // 获取Manager进行下载
            SDWebImageManager *manager;
            if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
                manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
            } else {
                manager = [SDWebImageManager sharedManager];
            }
            
             // 下载进度block
            __weak __typeof(self)wself = self;
            SDWebImageDownloaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
                wself.sd_imageProgress.totalUnitCount = expectedSize;
                wself.sd_imageProgress.completedUnitCount = receivedSize;
                if (progressBlock) {
                    progressBlock(receivedSize, expectedSize, targetURL);
                }
            };
            
             // 开启队列下载
            id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                __strong __typeof (wself) sself = wself;
                if (!sself) { return; }
                 // 完成后移除指示器
                [sself sd_removeActivityIndicator];
                // if the progress not been updated, mark it to complete state
                if (finished && !error && sself.sd_imageProgress.totalUnitCount == 0 && sself.sd_imageProgress.completedUnitCount == 0) {
                    sself.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                    sself.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
                }
                BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
                 // 是否不显示图片
                BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                          (!image && !(options & SDWebImageDelayPlaceholder)));
                SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                    if (!sself) { return; }
                    if (!shouldNotSetImage) {
                        [sself sd_setNeedsLayout];
                    }
                    // 如果设置了不自动显示图片,则回调让调用者手动添加显示图片 程序return
                    if (completedBlock && shouldCallCompletedBlock) {
                        completedBlock(image, error, cacheType, url);
                    }
                };
                
                // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
                // OR
                // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
                if (shouldNotSetImage) {
                    dispatch_main_async_safe(callCompletedBlockClojure);
                    return;
                }
                
                UIImage *targetImage = nil;
                NSData *targetData = nil;
                 // 设置图片
                if (image) {
                    // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                    targetImage = image;
                    targetData = data;
                }
                 // 如果设置了延时显示占位图,则显示placeholder
                else if (options & SDWebImageDelayPlaceholder) {
                    // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                    targetImage = placeholder;
                    targetData = nil;
                }
                
                // check whether we should use the image transition
                SDWebImageTransition *transition = nil;
                if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
                    transition = sself.sd_imageTransition;
                }
                
                if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
                     // 使用gcd的任务组来监听任务的完成,做block回调
                    dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
                    dispatch_group_enter(group);
                    dispatch_main_async_safe(^{
                        [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
                    });
                    // ensure completion block is called after custom setImage process finish
                    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
                        callCompletedBlockClojure();
                    });
                } else {
                    dispatch_main_async_safe(^{
                        [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
                        callCompletedBlockClojure();
                    });
                }
            }];
            //重新绑定operation到当前self
            [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        } else {
             // 移除菊花,显示error为nil的错误。
            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);
                }
            });
        }
    }
    

    相关文章

      网友评论

          本文标题:SDWebImage源码学习(三) --- WebCache C

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