SDWebImage 源码阅读(二)

本文详细解析了 SDWebImage 框架中的 SDWebImageManager 类如何处理图片的缓存与下载流程,包括从缓存获取图片、发起网络请求、图片处理及最终的回调过程。

我们在上一篇文章介绍了 SDWebImage 框架的 UIImageView+WebCache 扩展类,主要功能就是设计了严密的判断,保证运用该框架的其他开发者能够在各种环境下获取图片。这篇文章主要是介绍 SDWebImageManager 中从缓存获取图片或者从网络下载图片。

第一部分:获取图片对应的 key

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    // 调用这个方法必须实现 completedblock
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");


    // 下面两段代码都是为了保护 url 逻辑性


    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }


    // 创建一个 operation 
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    // 判断当前的url,是否包含在下载失败的url集合中,在访问failedURLs集合时要加锁,防止其他线程对其进行操作
    BOOL isFailedUrl = NO;

    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }

    // 如果url为空 || url存在于下载失败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;
    }

    // 将 operation 加到运行中的url集合中
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    // 从缓存中根据url取出key(其实key就是url的string)
    NSString *key = [self cacheKeyForURL:url];

    // 根据key去查找对应的图片 ???
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {  
    ...  // 详情见 第二部分
}];
    return operation;
}

第二部分:根据 image 判断是否需要从网络获取

    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {  
    ...          
}];

上面的代码调用了 SDImageCache 中的 queryDiskCacheForKey: done: 方法,我们后续章节再研究这个类中的东西,我们先看看对这个方法中的 block 的处理,也就是结果的处理方法:

// 判断该任务是否已经cancel了
if (operation.isCancelled) {
    @synchronized (self.runningOperations) {
        [self.runningOperations removeObject:operation];
    }

    return;
}


// 先看 && 的后半段
// 注意后面的 A || B,如果 A 为真,那就不用判断 B 了
// 也就是说,如果 imageManager:shouldDownloadImageForURL: 方法没有实现,直接返回 YES
// 目前我在源码中并没有看到函数的实现,所以就当 if 的后半段恒为 YES
// 我们主要看 && 前面的 || 表达式
// 如果缓存中没找到 image,或者需要刷新内存,就执行 if 语句
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
    ...  // 详情见第三部分
}

// 从缓存中找到 image
else if (image) {
    dispatch_main_sync_safe(^{
        __strong __typeof(weakOperation) strongOperation = weakOperation;
        if (strongOperation && !strongOperation.isCancelled) {
            completedBlock(image, nil, cacheType, YES, url);
        }
    });

    // 将该 operation 从数组中移除
    @synchronized (self.runningOperations) {
        [self.runningOperations removeObject:operation];
    }
}
else {
    // Image not in cache and download disallowed by delegate
    // 又没有从缓存中获取到图片,shouldDownloadImageForURL 又返回 NO,不允许下载,悲催!
    // 所以 image 和 error 均传入 nil
    dispatch_main_sync_safe(^{
        __strong __typeof(weakOperation) strongOperation = weakOperation;
        if (strongOperation && !weakOperation.isCancelled) {
            completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
        }
    });
    @synchronized (self.runningOperations) {
        [self.runningOperations removeObject:operation];
    }
}

第三部分:初始化下载图片

if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
    ...  
}

上面判断 if 中实现的内容就是 image 需要网络获取部分,详情如下:

if (image && options & SDWebImageRefreshCached) {
   dispatch_main_sync_safe(^{
        // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
        // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
        // 如果图片在缓存中找到,但是options中有SDWebImageRefreshCached
        // 那么就尝试重新下载该图片,这样是NSURLCache有机会从服务器端刷新自身缓存
        completedBlock(image, nil, cacheType, YES, url);
    });
}

// download if no image or requested to refresh anyway, and download allowed by delegate

// 首先定义枚举值 downloaderOptions,并根据 options 来设置 downloaderOptions
// 基本上 options 和 downloaderOptions 是一一对应的,只需要注意最后一个选项 SDWebImageRefreshCached
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
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 (image && options & SDWebImageRefreshCached) {
    // force progressive off if image already cached but forced refreshing

   // 相当于downloaderOptions = downloaderOption & ~SDWebImageDownloaderProgressiveDownload);
    // ~ 位运算,取反
    downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
    // ignore image read from NSURLCache if image if cached but force refreshing

    // 相当于 downloaderOptions = (downloaderOptions | SDWebImageDownloaderIgnoreCachedResponse);
    // 因为SDWebImage有两种缓存方式,一个是SDImageCache,一个就是NSURLCache
    // 所以已经从SDImageCache获取了image,就忽略NSURLCache了。
    downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}

// 下载图片 (图片下载部分将在后续章节解读,这里先不介绍)
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
    ...  // 详情见 第四部分       
}];
operation.cancelBlock = ^{
    [subOperation cancel];

    @synchronized (self.runningOperations) {
        __strong __typeof(weakOperation) strongOperation = weakOperation;
        if (strongOperation) {
            [self.runningOperations removeObject:strongOperation];
        }
    }
};

第四部分:下载图片完成后 block 中的回调

图片下载部分将在后续章节讲解,这里先介绍一下图片下载完成后 block 中的内容:

__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
    // Do nothing if the operation was cancelled
    // See #699 for more details
    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
}
else if (error) {
    dispatch_main_sync_safe(^{
        if (strongOperation && !strongOperation.isCancelled) {
            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
        }
    });

    if (   error.code != NSURLErrorNotConnectedToInternet
        && error.code != NSURLErrorCancelled
        && error.code != NSURLErrorTimedOut
        && error.code != NSURLErrorInternationalRoamingOff
        && error.code != NSURLErrorDataNotAllowed
        && error.code != NSURLErrorCannotFindHost
        && error.code != NSURLErrorCannotConnectToHost) {
        @synchronized (self.failedURLs) {
            [self.failedURLs addObject:url];
        }
    }
}
else {

    // 如果是失败重试,那么将 url 从 failedURLs 中移除
    if ((options & SDWebImageRetryFailed)) {
        @synchronized (self.failedURLs) {
            [self.failedURLs removeObject:url];
        }
    }

    // 是否只能从内存中取出
    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

    // 如果需要刷新缓存,并且存在 image,并且没有新下载的 image
    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
        // Image refresh hit the NSURLCache cache, do not call the completion block
    }

    // 如果有新下载的 image,并且需要对 image 进行处理,并且实现了图片处理的代理方法
    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

            // 获取处理后的图片
            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

            if (transformedImage && finished) {

                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];

                // 将图片缓存到内存中(这里后续讲解)
                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
            }

            // 将图片传出
            dispatch_main_sync_safe(^{
                if (strongOperation && !strongOperation.isCancelled) {
                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                }
            });
        });
}
else {

    // 如果不需要处理,直接缓存到内存中
    if (downloadedImage && finished) {
        [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
    }

    dispatch_main_sync_safe(^{
        if (strongOperation && !strongOperation.isCancelled) {
            completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
        }
    });
}
}

if (finished) {
    @synchronized (self.runningOperations) {
       if (strongOperation) {
            [self.runningOperations removeObject:strongOperation];
        }
    }
}

总结

SDWebImageManager 类中主要是对图片下载和缓存方式的管理,以及对下载完成的图片,url 等信息的回调,承上启下的作用,承接了 UIImageView+WebCache 类,下接了 SDImageCache 类和 SDWebImageDownloader 类。

SDWebImage 源码解析 github 地址:https://github.com/Mayan29/SDWebImageAnalysis

遗留问题:
  • 根据 key 去缓存中查找对应的图片

    • SDImageCache 类

    • - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock

  • 下载图片

    • SDWebImageDownloader 类

    • - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock

  • 将图片存入缓存

    • SDImageCache 类

    • - (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值