SDWebImage源码学习之由浅入深一

本次学习从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  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  operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id) 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
}

你可能感兴趣的:(iOS开发)