iOS深入原理iOS底层源码

SDWebImage 加载图片流程

2020-09-07  本文已影响0人  神经骚栋

SDWebImage是老生常谈的三方,这篇博客算是一个笔记吧,记录下SDWebImage源码相关加载图片流程.

注1: 整体流程基于 SDWebImage 5.0.6 版本.

注2: 本文只对iOS执行流程进行分析.默认会去除 Mac开发的部分(带有 #if SD_UIKIT || SD_MAC).

SDWebImage 整体流程


我们通过官方的这张图可以看出整体流程,我们主要通过分类方法的形式直接接触SDWebImage的执行流程.

SDWebImage内部加载流程层级较多,所以我这里分为 对外流程SDWebImageManager 内部流程SDImageCache 内部流程SDWebImageDownloader 内部流程 四个模块进行执行代码流转说明.

对外流程


UIImageView + WebCache 或者 UIButton + WebCache

SDWebImage的三方入口通常是UIImageView + WebCache 或者 UIButton + WebCache 这种分类的 sd_setImageWithURL 方法.

- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;

不管是哪个分类的 sd_setImageWithURL 将统一进入 UIView + WebCache 的 sd_internalSetImageWithURL 方法.

- (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;
internalSetImageWithURL 内部流程
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        validOperationKey = NSStringFromClass([self class]);
    }
    self.sd_latestOperationKey = validOperationKey;
    [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];
        });
    }
        dispatch_main_async_safe(^{
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            }
        });
    if (url) {
        ..... 正常流程
    } else {
        ..... 错误流程
    }
    SDWebImageManager *manager = context[SDWebImageContextCustomManager];
    if (!manager) {
        manager = [SDWebImageManager sharedManager];
    }   

    SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        @strongify(self);
        NSProgress *imageProgress = self.sd_imageProgress;
        imageProgress.totalUnitCount = expectedSize;
        imageProgress.completedUnitCount = receivedSize;

        if (progressBlock) {
            progressBlock(receivedSize, expectedSize, targetURL);
        }
    };
    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) {
    .... 完成回调
}];

    // 把图片加载Operation 加到 NSMapTable中.
    [self sd_setImageLoadOperation:operation forKey:validOperationKey];

    [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];

SDWebImageManager 内部流程


SDWebImageManager 中的 loadImageWithURL
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;
    BOOL isFailedUrl = NO;
    if (url) {
        SD_LOCK(self.failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(self.failedURLsLock);
    }
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
        return operation;
    }
    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];
SDWebImageManager 中的 callCacheProcessForOperation
    BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
   [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
    NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
    @weakify(operation);
    operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
        .... 查询完成回调
    }];
    if (!operation || operation.isCancelled) {
        [self safelyRemoveOperationFromRunning:operation];
        return;
    }
    // Continue download process
    [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
SDWebImageManager 中的 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];
    if (shouldDownload) {
        .... 正常下载流程
    }else if (cachedImage) {
        [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
        [self safelyRemoveOperationFromRunning:operation];
    } else {
        [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
        [self safelyRemoveOperationFromRunning:operation];
    }
    if (cachedImage && options & SDWebImageRefreshCached) {
        SDWebImageMutableContext *mutableContext;
        if (context) {
            mutableContext = [context mutableCopy];
        } else {
            mutableContext = [NSMutableDictionary dictionary];
        }
        mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
        context = [mutableContext copy];
    }
    @weakify(operation);
    operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
        .... 完成回调      
    }];
    if (!operation || operation.isCancelled) {

    } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {

    } else if (error) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
        BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
                
        if (shouldBlockFailedURL) {
            SD_LOCK(self.failedURLsLock);
            [self.failedURLs addObject:url];
            SD_UNLOCK(self.failedURLsLock);
        }
    } else {
        if ((options & SDWebImageRetryFailed)) {
            SD_LOCK(self.failedURLsLock);
            [self.failedURLs removeObject:url];
            SD_UNLOCK(self.failedURLsLock);
        }
                
        [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
    }
            
    if (finished) {
        [self safelyRemoveOperationFromRunning:operation];
    }
SDWebImageManager 中的 callStoreCacheProccessForOperation

该方法是主要是进行缓存存储,缓存存储图片主要有两种形式,一种是通过Transform 处理过的图片,一种是普通的图片形式.

    SDImageCacheType storeCacheType = SDImageCacheTypeAll;
    if (context[SDWebImageContextStoreCacheType]) {
        storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
    }
    id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
    NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
    id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
    id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        @autoreleasepool {
            UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key];
            if (transformedImage && finished) {
                NSString *transformerKey = [transformer transformerKey];
                NSString *cacheKey = SDTransformedKeyForKey(key, transformerKey);
                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                NSData *cacheData;
                if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
                    cacheData = [cacheSerializer cacheDataWithImage:transformedImage  originalData:(imageWasTransformed ? nil : downloadedData) imageURL:url];
                } else {
                    cacheData = (imageWasTransformed ? nil : downloadedData);
                }
                [self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
            }
            [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
            }
        });
    if (downloadedImage && finished) {
        if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                @autoreleasepool {
                    NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
                    [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key cacheType:storeCacheType completion:nil];
                }
            });
        } else {
            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:storeCacheType completion:nil];
        }
    }

    [self callCompletionBlockForOperation:operation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];

SDImageCache 内部流程


对于 SDImageCache 的流转入口主要是 queryImageForKey 获取图片 以及 storeImage 存储图片两个入口,我们一一来看.

SDImageCache 的 queryImageForKey
    SDImageCacheOptions cacheOptions = 0;
    if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
    if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
    if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
    if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
    if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
    return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock];
SDImageCache 的 queryImageOperationForKey
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
    if (transformer) {
        NSString *transformerKey = [transformer transformerKey];
        key = SDTransformedKeyForKey(key, transformerKey);
    }
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if ((options & SDImageCacheDecodeFirstFrameOnly) && image.sd_isAnimated) {
        image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
    }
    BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
    NSOperation *operation = [NSOperation new];
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    void(^queryDiskBlock)(void) =  ^{
    };
    NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
    if (image) {
        diskImage = image;
        cacheType = SDImageCacheTypeMemory;
    } else if (diskData) {
        cacheType = SDImageCacheTypeDisk;
        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);
            });
        }
    }
    if (shouldQueryDiskSync) {
        dispatch_sync(self.ioQueue, queryDiskBlock);
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
SDImageCache 的 storeImage

storeImage方法主要是用来存储图片缓存,根据参数进行缓存存储和沙盒存储.

    if (toMemory && self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = image.sd_memoryCost;
        [self.memCache setObject:image forKey:key cost:cost];
    }
    dispatch_async(self.ioQueue, ^{
        @autoreleasepool {
            NSData *data = imageData;
            if (!data && image) {
                SDImageFormat format;
                if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
                    format = SDImageFormatPNG;
                } else {
                    format = SDImageFormatJPEG;
                }
                data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
            }
            [self _storeImageDataToDisk:data forKey:key];
        }
        
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });

SDWebImageDownLoader 内部流程


requestImageWithURL
    SDWebImageDownloaderOptions downloaderOptions = 0;
    if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
    if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
    if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
    if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
    if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
    if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
    if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
    if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
    if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
    if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
    if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
    
    if (cachedImage && options & SDWebImageRefreshCached) {
        downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
        downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
    }
    return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
downloadImageWithURL
    if (url == nil) {
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
            completedBlock(nil, nil, error, YES);
        }
        return nil;
    }
    SD_LOCK(self.operationsLock);
    NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];

    if (!operation || operation.isFinished || operation.isCancelled) {
            .... 相关操作
    }

    SD_UNLOCK(self.operationsLock);
    operation = [self createDownloaderOperationWithUrl:url options:options context:context];
    if (!operation) {
        SD_UNLOCK(self.operationsLock);
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
            completedBlock(nil, nil, error, YES);
        }
        return nil;
    }
    @weakify(self);
    operation.completionBlock = ^{
        @strongify(self);
        if (!self) {
            return;
        }
        SD_LOCK(self.operationsLock);
        [self.URLOperations removeObjectForKey:url];
        SD_UNLOCK(self.operationsLock);
    };
    self.URLOperations[url] = operation;
    [self.downloadQueue addOperation:operation];
    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    token.downloader = self;
    return token;

到此,下载流程基本结束.

SDMemoryCache的相关


SDMemoryCache 是继承于 NSCache, NSCache具有线程安全,会根据内存警告自动做删减操作,不拷贝键等特点.

SDMemoryCache 含有一个NSMapTable类型的弱引用哈希表,这是为什么呢? 主要是因为 NSCache如果因为内存不足就会清理时,假设对象还没有被销毁,那么我们可以通过弱引用表找到该对象,增加了Cache的广度.

关于SDWebImage的提前对图片解码操作


在 SDImageCache 从沙盒读取出来的图片数据并不是直接存入缓存中,而是相对图片进行解码操作.这样可以减少后期CPU的计算工作.对提高性能有很大帮助.图片的解码工作主要是通过SDImageCoderHelper 类中的 decodedImageWithImagedecodedAndScaledDownImageWithImage 方法完成的.具体操作如下所示.

+ (UIImage *)decodedImageWithImage:(UIImage *)image {
    if (![self shouldDecodeImage:image]) {
        return image;
    }
    
    CGImageRef imageRef = [self CGImageCreateDecoded:image.CGImage];
    if (!imageRef) {
        return image;
    }
    UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
    CGImageRelease(imageRef);
    decodedImage.sd_isDecoded = YES;
    decodedImage.sd_imageFormat = image.sd_imageFormat;
    return decodedImage;
}

关于SDWebImage的多线程问题


在 SDWebImage 的线程安全上基本上是用了信号量来实现线程安全.这个到处可见.如下所示.

    SD_LOCK(self.failedURLsLock);
    [self.failedURLs removeObject:url];
    SD_UNLOCK(self.failedURLsLock);

对于沙盒操作实际上是放在一个串行队列中.这样可以保证访问任务的顺序执行.对于异步还是同步主要是配置项的具体配置.

    _ioQueue = dispatch_queue_create("com.hackemist.SDImageCache", DISPATCH_QUEUE_SERIAL);

总结


SDWebImage 好几年前就翻了源码,但是只是简单的看了看,没有做记录,现在就接着面试的机会做个记录吧,如果有问题,欢迎指导~ 骚栋谢谢各位大佬了.

上一篇 下一篇

猜你喜欢

热点阅读