SDWebImage 源码阅读(二)

来源:互联网 发布:重庆网络推广怎样 编辑:程序博客网 时间:2024/05/17 04:50

我们在上一篇文章介绍了 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])) {    ...  // 详情见第三部分}// 从缓存中找到 imageelse 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 是一一对应的,只需要注意最后一个选项 SDWebImageRefreshCachedSDWebImageDownloaderOptions 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

0 0
原创粉丝点击