读SDWebImage源码(一)

SDWebImage(以下简称SD)作为iOS开发不可或缺的图片工具库、GitHub上18K star的存在,不得不说,很强大。那么, 抱着敬畏之心,我们来看一看它的源码吧。

说到SD,最先想到的就是下面这个方法了,那么就先从这它开始一步步看具体实现吧。

/**
 *  异步下载并缓存
 *  第一个参数是图片URL,第二个参数是占位图
 */
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;

一层一层点进去,最终进入这个方法:

// 所有设置图片调用的方法最终都会调用这个方法
- (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;

它的实现:

/**
  *  首先,取消先前的下载任务
  *  因为sd_setImageWithURL:placeholderImage:这个方法我们并没有传入operationKey,
  *  所以它创建了一个新key,然后通过runtime添加为属性
  */
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];//取消先前的下载任务
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//动态添加属性
/**
  *  之后,设置占位图
  *  这里options & SDWebImageDelayPlaceholder用了按位与操作
  *  举个例子:3 & 7 -> 00000011 & 00000111 = 00000011
  *  也就是说,如果我们选择的options中包含SDWebImageDelayPlaceholder,就设置占位图
  */
if (!(options & SDWebImageDelayPlaceholder)) {
    dispatch_main_async_safe(^{
        [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];//设置占位图
    });
}
/**
 *  接下来,判断url是否为nil
 *  如果是nil,block回调异常等信息
 *  否则执行下载操作
 */
if (url) {
    //下载操作
    __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) {
        if (!sself) {
            return;// 如果self被释放,return
        }
        //拿到image,给imageView设置图片并回调
        coding....
    }];
} else {
    //回调异常...
}
下载回调中特别有意思的宏, strcmp函数判断当前队列与主队列的label是否相同,从而得出当前队列是否为主队列。如果是,因为主队列执行能等同于主线程执行,所以直接回调; 否则回到主线程回调。
#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

下载操作:

/**
 *  第一个参数,图片url
 *  第二个参数,下载设置
 *  第三个参数,进度回调
 *  第四个参数,完成回调
 */
- (id )loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock;
下载具体实现:
//生成一个新的任务
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
//判断url是不是失败过的URL
BOOL isFailedUrl = NO;
if (url) {
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
}
//url为nil或者 URL下载失败且设置了失败不再重试
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
    //失败处理
    dispatch_main_sync_safe(^{ //线程锁,保证线程安全
        NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
        completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
    });
    return operation;
}
//将任务添加到下载队列并生成key
@synchronized (self.runningOperations) {
    [self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];//根据URL生成key

以下为获取Image操作,因为代码过长,只说说我对代码的理解:

  • 首先,通过key在缓存中查找是否存在对应image。若存在,回调image并return;若不存在,继续尝试从磁盘中获取图片,如果找到了,更新缓存与磁盘的存储数据。不管image是不是存在,回调image;
  • 得到NSOperation对象,目的是为了使用NSOperation的取消方法,通过设置取消方法来达到取消异步从磁盘中读取图片的操作;
  • 在取消操作的block中取消下载,从下载队列中移除下载操作;
  • 根据operation.isCancelled判断是否继续进行。不进行的话从下载队列中移除;
  • 如果Image存在,complete回调;
  • 调用downloadImageWithURL: options: progress: completed:下载Image,完成后将图片存进缓存并complete回调;
  • 移除下载操作;
  • return operation。

总结:

  • 涉及下载的操作都放在支线程执行;
  • 图片缓存;
  • 有重试机制;
  • 利用了NSOperation的可控性进行耗时操作。

谢谢@大尾巴熊Johnny指出文章的错误。

你可能感兴趣的:(读SDWebImage源码(一))