SDWebImage源码学习之由浅入深一

来源:互联网 发布:杭州火车东站到淘宝城 编辑:程序博客网 时间:2024/06/14 11:22

本次学习从SDWebImage 常用的图片加载开始 ,由浅入深的剖析实现过程

    UIImageView *imgV = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];    [imgV sd_setImageWithURL:[NSURL URLWithString:@""] placeholderImage:nil];

首先进入方法 ,该方法是UIImageView 的分类中实现 UIImageView+WebCache.h

- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];}

继续深入

- (void)sd_setImageWithURL:(nullable NSURL *)url          placeholderImage:(nullable UIImage *)placeholder                   options:(SDWebImageOptions)options                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock                 completed:(nullable SDExternalCompletionBlock)completedBlock { // 此处为UIImageView 的父类UIView 的分类方法 UIView+WebCache    [self sd_internalSetImageWithURL:url                    placeholderImage:placeholder                             options:options                        operationKey:nil                       setImageBlock:nil                            progress:progressBlock                           completed: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 {   // 首先会根据operationKey 传入的值来设置validOperationKey,当为nil时,设置为当前类名的字符串   // 我们在调用 sd_setImageWithURL: placeholderImage: 时,这里传入的是nil 所以此处validOperationKey = @"UIImageView"    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);  // 该方法将根据validOperationKey的值去取消对应的图片加载任务  // 该方法的详细实现在下面    [self sd_cancelImageLoadOperationWithKey:validOperationKey];// 将当前url 通过imageURLKey 与self 绑定,由于self不同,这样就实现了 url 与对应的UIImageView绑定。    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);// 在本例的简单调用中options = 0,所以 判断为真,进入分支// dispatch_main_async_safe() 这个宏是为了判断是否在主线程,如果不在则回到主线程调用。其实现见下 if (!(options & SDWebImageDelayPlaceholder)) {        dispatch_main_async_safe(^{// 确保在主线程下调用// 此处由参数可见 是为了先设置placeholder 即占位图,实现没有图片时显示占位图,本例中 placeholder,setImageBlock  参数均为nil// 该方法实现见下          [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];        });    }    if (url) {        // check if activityView is enabled or not        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 {                    if ((options & SDWebImageDelayPlaceholder)) {                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];                        [sself sd_setNeedsLayout];                    }                }                if (completedBlock && finished) {                    completedBlock(image, error, cacheType, url);                }            });        }];        [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);            }        });    }}

dispatch_main_async_safe() 的实现,
dispatch_queue_get_label获取当前队列标签,使用strcmp()函数和主线程队列标签比较

#ifndef dispatch_main_async_safe#define dispatch_main_async_safe(block)\    if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\        block();\    } else {\        dispatch_async(dispatch_get_main_queue(), block);\    }#endif

该方法实现在UIView的分类UIView+WebCacheOperation

typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;- (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];    }}
- (SDOperationsDictionary *)operationDictionary {// 通过变量绑定的方式,获取到operations SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);    if (operations) {        return operations;    }// 第一次进入时,operations需要新创建,并进行变量绑定给loadOperationKey,然后返回   operations = [NSMutableDictionary dictionary];    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);    return operations;}
- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock {    if (setImageBlock) { // 本例中为nil         setImageBlock(image, imageData);        return;    }// 下面代码就比较简单了,根据self的类执行不同方法设置占位图。 #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}