美文网首页
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