美文网首页
读SDWebImage(一)-4.4.6

读SDWebImage(一)-4.4.6

作者: 涨了翅膀的小绵羊 | 来源:发表于2021-01-31 12:13 被阅读0次

    按照我们调用的顺序,首先要了解UIView+WebCache和UIView+WebCacheOperation,文件中最重要的方法,其他控件方法最终都会调用到此方法。

    - (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                      placeholderImage:(nullable UIImage *)placeholder
                               options:(SDWebImageOptions)options
                          operationKey:(nullable NSString *)operationKey
                 internalSetImageBlock:(nullable SDInternalSetImageBlock)setImageBlock
                              progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                             completed:(nullable SDExternalCompletionBlock)completedBlock
                               context:(nullable NSDictionary<NSString *, id> *)context
    

    方法中主要是3大步骤:

    1. 取消URL的请求操作。
    2. 设置占位图,加载&&显示图片。
    3. 保存第2步的操作对象。

    1.取消URL的操作对象

    操作的取消是根据方法参数operationKey为key获取集合sd_operationDictionary内的操作对象,然后执行取消操作,并移除操作对象。

    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    
    - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
        if (key) {
            // Cancel in progress downloader from queue
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            id<SDWebImageOperation> operation;
            
            @synchronized (self) {
                operation = [operationDictionary objectForKey:key];
            }
            if (operation) {
                if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                    [operation cancel];
                }
                @synchronized (self) {
                    [operationDictionary removeObjectForKey:key];
                }
            }
        }
    }
    

    此方法在UIView+WebCacheOperation中。这里的operation是要满足< SDWebImageOperation>代理的,对集合操作保证同时只有一个线程在操作。

    在这列列举一下视图分类的key分别是什么,
    1、UIButton+WebCache的key是UIButtonImageOperation_state,_state是UIControlState类型值(背景图片key是UIButtonBackgroundImageOperation_state)。
    2、UIImageView+WebCache的key是UIImageView类名。
    3、UIImageView+HighlightedWebCache的key是UIImageViewImageOperationHighlighted。

    2.设置占位图并加载图片

    设置占位图

    options不包含SDWebImageDelayPlaceholder枚举值时,线程安全设置占位图。

    if (!(options & SDWebImageDelayPlaceholder)) {
            if (group) {
                dispatch_group_enter(group);
            }
            dispatch_main_async_safe(^{
                [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
            });
    }
    

    占位图内部调用是无动画方式设置图片,除了UIImageView+WebCache直接调用setImage:,另外2个视图分类通过setImageBlock回调设置图片。(具体实现可以找到方法看一下)。

    加载图片

    这里只讲获取到图片后,显示/回调图片的逻辑。(加载图片的逻辑后面讲)。
    这里有2种方式显示图片:
    1.将image对象回调到completedBlock显示,条件:
    image && (options & SDWebImageAvoidAutoSetImage) 或者
    !image && !(options & SDWebImageDelayPlaceholder)
    2.UIView+WebCache根据对象类型以及动画对象sd_imageTransition,是否动画显示image,然后再回调completedBlock。

    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; }
    #if SD_UIKIT
                [sself sd_removeActivityIndicator];
    #endif
                // 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];
                    }
                    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;
                } else if (options & SDWebImageDelayPlaceholder) {
                    // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                    targetImage = placeholder;
                    targetData = nil;
                }
                
    #if SD_UIKIT || SD_MAC
                // check whether we should use the image transition
                SDWebImageTransition *transition = nil;
                if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
                    transition = sself.sd_imageTransition;
                }
    #endif
                dispatch_main_async_safe(^{
                    if (group) {
                        dispatch_group_enter(group);
                    }
    #if SD_UIKIT || SD_MAC
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
    #else
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
    #endif
                    if (group) {
                        // compatible code for FLAnimatedImage, because we assume completedBlock called after image was set. This will be removed in 5.x
                        BOOL shouldUseGroup = [objc_getAssociatedObject(group, &SDWebImageInternalSetImageGroupKey) boolValue];
                        if (shouldUseGroup) {
                            dispatch_group_notify(group, dispatch_get_main_queue(), callCompletedBlockClojure);
                        } else {
                            callCompletedBlockClojure();
                        }
                    } else {
                        callCompletedBlockClojure();
                    }
                });
            }];
    

    3. 保存第2步的操作对象

    根据第 1 步描述的key,先取消操作,再将新的操作对象缓存到sd_operationDictionary内。

    [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    
    - (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
        if (key) {
            [self sd_cancelImageLoadOperationWithKey:key];
            if (operation) {
                SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
                @synchronized (self) {
                    [operationDictionary setObject:operation forKey:key];
                }
            }
        }
    }
    

    到了这里主流程UIView+WebCache、UIView+WebCacheOperation就可以结束了。
    可以跳转到SDWebImage(二) 查看图片记载的流程。

    UIView+WebCache、UIView+WebCacheOperation其他功能方法讲解

    UIView+WebCache

    1.获取当前图片地址

    - (nullable NSURL *)sd_imageURL {
        return objc_getAssociatedObject(self, &imageURLKey);
    }
    

    2.获取进度对象

    - (NSProgress *)sd_imageProgress {
        NSProgress *progress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
        if (!progress) {
            progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
            self.sd_imageProgress = progress;
        }
        return progress;
    }
    
    - (void)setSd_imageProgress:(NSProgress *)sd_imageProgress {
        objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

    3.取消当前请求操作。

    - (void)sd_cancelCurrentImageLoad {
        [self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
    }
    

    这里是根据类名字符串来取消操作,根据上述第一步key的生成,得知,
    UIImageView+WebCache才能通过此方法获取操作对象,指向取消功能。
    UIButton+WebCache类有根据第 1 步的生成的key取消操作的功能方法。
    UIImageView+HighlightedWebCache 是没有取消操作功能的,目前为止此类型图片请求时无法取消的。

    4.设置动画对象

    /**
     The image transition when image load finished. See `SDWebImageTransition`.
     If you specify nil, do not do transition. Defautls to nil.
     */
    @property (nonatomic, strong, nullable) SDWebImageTransition *sd_imageTransition;
    

    SDWebImageTransition对象详细使用,可以跳转XXXXX

    5.ActivityIndicatorView显示/隐藏/style。

    - (UIActivityIndicatorView *)activityIndicator {
        return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
    }
    
    - (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
        objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
    }
    
    - (void)sd_setShowActivityIndicatorView:(BOOL)show {
        objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, @(show), OBJC_ASSOCIATION_RETAIN);
    }
    
    - (BOOL)sd_showActivityIndicatorView {
        return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
    }
    
    - (void)sd_setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
        objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
    }
    
    - (int)sd_getIndicatorStyle{
        return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
    }
    
    - (void)sd_addActivityIndicator {
        dispatch_main_async_safe(^{
            if (!self.activityIndicator) {
                self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self sd_getIndicatorStyle]];
                self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
            
                [self addSubview:self.activityIndicator];
                
                [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
                                                                 attribute:NSLayoutAttributeCenterX
                                                                 relatedBy:NSLayoutRelationEqual
                                                                    toItem:self
                                                                 attribute:NSLayoutAttributeCenterX
                                                                multiplier:1.0
                                                                  constant:0.0]];
                [self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
                                                                 attribute:NSLayoutAttributeCenterY
                                                                 relatedBy:NSLayoutRelationEqual
                                                                    toItem:self
                                                                 attribute:NSLayoutAttributeCenterY
                                                                multiplier:1.0
                                                                  constant:0.0]];
            }
            [self.activityIndicator startAnimating];
        });
    }
    
    - (void)sd_removeActivityIndicator {
        dispatch_main_async_safe(^{
            if (self.activityIndicator) {
                [self.activityIndicator removeFromSuperview];
                self.activityIndicator = nil;
            }
        });
    }
    
    
    UIView+WebCacheOperation

    这里的功能是sd_operationDictionary对象,对operation对象的添加、取消操作功能。通过NSMapTable集合的NSPointerFunctionsStrongMemory、NSPointerFunctionsWeakMemory,对key是copy,对value是弱引用。通过@synchronized对集合对象线程安全操作。

    static char loadOperationKey;
    
    // key is copy, value is weak because operation instance is retained by SDWebImageManager's runningOperations property
    // we should use lock to keep thread-safe because these method may not be acessed from main queue
    typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
    
    @implementation UIView (WebCacheOperation)
    
    - (SDOperationsDictionary *)sd_operationDictionary {
        @synchronized(self) {
            SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
            if (operations) {
                return operations;
            }
            operations = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
            objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            return operations;
        }
    }
    
    - (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
        if (key) {
            [self sd_cancelImageLoadOperationWithKey:key];
            if (operation) {
                SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
                @synchronized (self) {
                    [operationDictionary setObject:operation forKey:key];
                }
            }
        }
    }
    
    - (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
        if (key) {
            // Cancel in progress downloader from queue
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            id<SDWebImageOperation> operation;
            
            @synchronized (self) {
                operation = [operationDictionary objectForKey:key];
            }
            if (operation) {
                if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
                    [operation cancel];
                }
                @synchronized (self) {
                    [operationDictionary removeObjectForKey:key];
                }
            }
        }
    }
    
    - (void)sd_removeImageLoadOperationWithKey:(nullable NSString *)key {
        if (key) {
            SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
            @synchronized (self) {
                [operationDictionary removeObjectForKey:key];
            }
        }
    }
    

    到这里UIView+WebCache 和UIView+WebCacheOperation 的功能就讲完了。

    相关文章

    相关文章

      网友评论

          本文标题:读SDWebImage(一)-4.4.6

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