SDWebImage源码分析(1)——查找缓存和网络加载

2020-12-30  本文已影响0人  无悔zero

SDWebImage是很常用的一个加载图片的框架,特别是在列表中,非常需要它。我们一起来看看它的源码流程,看看下面例子:

 [self.image sd_setImageWithURL:nil];
  1. 直接从入口找到这:
@implementation UIImageView (WebCache)
...
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                             context:context
                       setImageBlock:nil
                            progress:progressBlock
                           completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
                               if (completedBlock) {
                                   completedBlock(image, error, cacheType, imageURL);
                               }
                           }];
}
@implementation UIView (WebCache)
...
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                           context:(nullable SDWebImageContext *)context
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDImageLoaderProgressBlock)progressBlock
                         completed:(nullable SDInternalCompletionBlock)completedBlock {
    ...
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];//先取消旧的任务
    self.sd_imageURL = url;
    //如果不是延迟设置,就直接在主线程设置占位图
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];//设置占位图
        });
    }
    
    if (url) {
        ...
#if SD_UIKIT || SD_MAC
        [self sd_startImageIndicator];//开启指示器
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
        SDWebImageManager *manager = context[SDWebImageContextCustomManager];//管理者
        ...
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
            ...
            dispatch_async(dispatch_get_main_queue(), ^{
                [imageIndicator updateIndicatorProgress:progress];//更新显示进度
            });
        }
        ...
        @weakify(self);
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            @strongify(self);
            if (!self) { return; }
            // if the progress not been updated, mark it to complete state
            if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
                imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
            }
            
#if SD_UIKIT || SD_MAC
            if (finished) {
                [self sd_stopImageIndicator];//停止指示器
            }
#endif
            ...
        }];
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];//保存operation
        } else { ... }
}

然后可以看到,先进行取消任务,处理细节UI,然后使用SDWebImageManager来处理图片,并返回SDWebImageOperation

  1. SDWebImageOperation最后会保存到SDOperationsDictionary中:
@implementation UIView (WebCacheOperation)
...
- (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];//添加
            }
        }
    }
}
  1. 接着来看SDWebImageOperation是怎么创建和启动的:
@implementation SDWebImageManager
...
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    ...
    //耦合缓存查找任务和网络加载任务
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    ...
    //查找缓存和网络加载
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    return operation;
}

返回的是SDWebImageCombinedOperation,其作用其实就是耦合了loaderOperation网络加载任务和cacheOperation缓存查找任务:

@interface SDWebImageCombinedOperation ()

@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> loaderOperation;
@property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> cacheOperation;
@property (weak, nonatomic, nullable) SDWebImageManager *manager;

@end
  1. 如果需要查找缓存,SDWebImageManager会先查找缓存,再网络加载图片;否则,直接网络加载图片:
@implementation SDWebImageManager
...
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(nullable SDInternalCompletionBlock)completedBlock {
    ...
    if (shouldQueryCache) {
        NSString *key = [self cacheKeyForURL:url context:context];//获取图片对应的key
        //先查找缓存
        @weakify(operation);
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);
            ...
            // Continue download process
            [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];//再加载图片
        }];
    } else {
        // Continue download process
        [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
    }
}
  1. 先来看看查找任务:
@implementation SDWebImageManager
...
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
    ...   
    return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}
@implementation SDImageCache
...
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    ...
    if (queryCacheType != SDImageCacheTypeDisk) {
        image = [self imageFromMemoryCacheForKey:key];//首先检查内存缓存
    }
    ...
    void(^queryDiskBlock)(void) =  ^{
        ...
        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];//然后检查磁盘缓存
            ...
        }
    };
    // Query in ioQueue to keep IO-safe
    if (shouldQueryDiskSync) {
        dispatch_sync(self.ioQueue, queryDiskBlock);
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    return operation;
}

5-1. 首先会查找内存缓存:

@implementation SDImageCache
...
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
    return [self.memoryCache objectForKey:key];
}
@implementation SDMemoryCache
...
- (id)objectForKey:(id)key {
    ...
    if (key && !obj) {
        // Check weak cache
        SD_LOCK(_weakCacheLock);
        obj = [self.weakCache objectForKey:key];//从内存缓存获取
        SD_UNLOCK(_weakCacheLock);
        ...
        }
    }
    return obj;
}

5-2. 再去查找磁盘缓存:

@implementation SDImageCache
...
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    ...
    NSData *data = [self.diskCache dataForKey:key];
    ...
    return data;
}
@implementation SDDiskCache
...
- (NSData *)dataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];//从沙盒读取
    ...
    return nil;
}
  1. 回到第4步,如果没有找到缓存,自然就去网络加载图片:
@implementation SDWebImageManager
...
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
                                    url:(nonnull NSURL *)url
                                options:(SDWebImageOptions)options
                                context:(SDWebImageContext *)context
                            cachedImage:(nullable UIImage *)cachedImage
                             cachedData:(nullable NSData *)cachedData
                              cacheType:(SDImageCacheType)cacheType
                               progress:(nullable SDImageLoaderProgressBlock)progressBlock
                              completed:(nullable SDInternalCompletionBlock)completedBlock {
    ...
    if (shouldDownload) {
        ...
        //创建loaderOperation
        operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { ... }
    } else if (cachedImage) {
        ...
    } else { ... }
@implementation SDWebImageDownloader {
...
- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
    ...
    return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}

6-1. 到这一步,创建了网络加载任务的operation,然后添加到self.downloadQueue中,添加后内部会自动的调用start方法执行任务:

@implementation SDWebImageDownloader {
...
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    ...
    if (!operation || operation.isFinished || operation.isCancelled) {
        operation = [self createDownloaderOperationWithUrl:url options:options context:context];
        ...
        [self.downloadQueue addOperation:operation];//添加后内部会自动的调用start方法执行任务
    } else { ... }
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];//包装operation
    ...
    return token;
}

6-2. 在创建SDWebImageDownloaderOperation(网络加载任务的operation)时,默认情况下网络缓存策略会设置为NSURLRequestReloadIgnoringLocalCacheData,防止重复缓存:

@implementation SDWebImageDownloader {
...
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
                                                                                  options:(SDWebImageDownloaderOptions)options
                                                                                  context:(nullable SDWebImageContext *)context {
    ...
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;//默认不设置,防止重复缓存,所以默认忽略本地缓存
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
    ...
    NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
    ...
    return operation;
}

6-3. 回到第6-1步,创建完SDWebImageDownloaderOperation添加到self.downloadQueue后,自动执行SDWebImageDownloaderOperationstart方法,开始网络加载图片:

@implementation SDWebImageDownloaderOperation
...
- (void)start {
    @synchronized (self) {
        ...
        NSURLSession *session = self.unownedSession;
        if (!session) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];//创建session
            self.ownedSession = session;
        }
        ...
        self.dataTask = [session dataTaskWithRequest:self.request];//创建task
        self.executing = YES;
    }

    if (self.dataTask) {
        ...
        [self.dataTask resume];//启动请求
        ...
    } else ... }

简单来说,SDWebImage处理图片时,会先取消旧的任务,处理指示器和进度,接着优先查找缓存显示图片,再通过网络加载显示图片。

网络请求时,在下面回调中把NSCachedURLResponse等于nil可以避免产生本地缓存:


·

上一篇下一篇

猜你喜欢

热点阅读