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

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

作者: 无悔zero | 来源:发表于2020-12-30 08:16 被阅读0次

    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;
    }
    
    • 返回时,用SDWebImageDownloadToken包装了网络加载任务的operation。我们可以发现,SDWebImageCombinedOperationloaderOperationcacheOperation都不是真正意义上的网络加载任务和缓存查找任务,创建时cacheOperation就已经进行缓存查找,loaderOperation则是包装了网络加载任务的SDWebImageDownloadToken

    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可以避免产生本地缓存:


    ·

    相关文章

      网友评论

          本文标题:SDWebImage源码分析(1)——查找缓存和网络加载

          本文链接:https://www.haomeiwen.com/subject/bpnxoktx.html