SDWebImage 原理解析及核心代码分析

简介

SDWebImage 是一个非常强大的图片加载框架,从一开始工作都在用它,都知道他是先内存检查 再磁盘检查 都没有最后才走网络下载,那么他实现的代码究竟是什么逻辑呢,今天我们就来分析他的核心代码。

主讲5个类

  1. UIImageView+WebCache.h
  2. UIView+WebCache.h
  3. UIView+WebCacheOperation.h
  4. SDWebImageManager.h
  5. SDWebImageDownloader.h
  6. SDWebImageDownloaderOperation.m

UIImageView+WebCache.h
这里不管调用任何方法 最终都会到下面这个方法中

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock{}

通过这个类的查看发现 SD的代码复用很高 也发现了人家的设计技巧 从最原始的没有placeholderImage 到 没有SDWebImageOptions 一层一层调用 抛出来的API非常易用。

*** UIView+WebCache.h ***
调用 sd_setImageWithURL 最终会到 下面这个方法。接下来我们分析这个方法里的代码 我把一些判断信息 和进度 信息去掉

 - (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 {
  }

== 代码分析 ==

这一行是取消当前view上的下载操作 如果之前已经有下载任务 现在再来一个下载任务 之前的下载完全没有必要
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
用于管理图片下载 上层调用API
manager = [SDWebImageManager sharedManager]; 
接下来执行下边的方法
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {}

来到SDWebImageManager.h 代码分析

SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
BOOL isFailedUrl = NO;  判断是否走失败逻辑
SD_LOCK(self.runningOperationsLock);
 [self.runningOperations addObject:operation]; 将正在进行的操作加入到队列中
SD_UNLOCK(self.runningOperationsLock);
下面分析这个方法 这里是决定我们从缓存 磁盘 还是网络拿图片的入口
[self callCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock]; 

callCacheProcessForOperation

BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0; 判断是否可以拿缓存 我们这里只看可以拿缓存的情况
if (shouldQueryCache) {
        id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
        NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter]; // 拿到缓存图片key
 operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
  
        }];
}

来到SDImageCache 的queryCacheOperationForKey方法

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock{
从内存缓存中拿
UIImage *image = [self imageFromMemoryCacheForKey:key];
检查是否允许磁盘查找
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync)); 
从磁盘查找
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
如果内存中没有就把磁盘中的读出 重新放入内存缓存中
if (image) {
                diskImage = image;
                cacheType = SDImageCacheTypeMemory;
            } else if (diskData) {
                cacheType = SDImageCacheTypeDisk;
                // decode image data only if in-memory cache missed
                diskImage = [self diskImageForKey:key data:diskData options:options context:context];
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = diskImage.sd_memoryCost;
                    [self.memCache setObject:diskImage forKey:key cost:cost];
                }
            }
}
执行回调
if (doneBlock) {
                if (shouldQueryDiskSync) {
                    doneBlock(diskImage, diskData, cacheType);
                } else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, cacheType);
                    });
                }
            }

来到SDWebImageManager.h callDownloadProcessForOperation

这一大段都是用来判断有没有必要从网络读取图片的操作
BOOL shouldDownload = (options & SDWebImageFromCacheOnly) == 0;
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    shouldDownload &= [self.imageLoader canRequestImageForURL:url];
    接下来分析下面的方法
    operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage

来到SDWebImageDownloader requestImageWithURL

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
        
}
self.URLOperations[url] = operation; 设置键值映射 以url为键。操作为值 用于判断当同一个url的操作已经有了就不用再执行了
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
这一行代码很重要 将所有的回调方法进行保存
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
执行了下边这行代码 此时 下载任务开始。
[self.downloadQueue addOperation:operation];

来到SDWebImageDownloaderOperation.m

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    SD_LOCK(self.callbacksLock);
    [self.callbackBlocks addObject:callbacks];  数组中包含了以 completed 为键 以回调函数为值的字典 一会会用到
    SD_UNLOCK(self.callbacksLock);
    return callbacks;
}

基于 NSURLSession 对图片进行下载

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
 self.ownedSession = session;
 self.dataTask = [session dataTaskWithRequest:self.request];
 [self.dataTask resume];
 基于系统的 session 执行下载任务

下载成功回调

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSData *imageData = [self.imageData copy]; // 在另一个回调方法中 已经给 self.imageData赋值了
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
// 下边这个方法就是将addHandlersForProgress所有加入的回调 全部执行一遍  
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
                            imageData:(nullable NSData *)imageData
                                error:(nullable NSError *)error
                             finished:(BOOL)finished {
    NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
    dispatch_main_async_safe(^{
        for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
            completedBlock(image, imageData, error, finished);
        }
    });
}

补充一个方法

- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
#if SD_UIKIT || SD_MAC
    [self sd_setImage:image imageData:imageData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:nil cacheType:cacheType imageURL:imageURL];
#else
    // watchOS does not support view transition. Simplify the logic
    if (setImageBlock) {
        setImageBlock(image, imageData, cacheType, imageURL);
    } else if ([self isKindOfClass:[UIImageView class]]) {
        UIImageView *imageView = (UIImageView *)self;
        [imageView setImage:image];
    }
#endif
}

当所有的下载都完成了以后会调用上边的方法 将下载好的图片进行赋值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值