SDWebImage 源码阅读笔记(一)
2016-11-14 17:34
603 查看
简介
Asynchronous image downloader with cache support as a UIImageView category.言简意赅:SDWebImage 以 UIImageView category(分类)的形式,来支持图片的异步下载与缓存。
其提供了以下功能:
以 UIImageView 的分类,来支持网络图片的加载与缓存管理
一个异步的图片加载器
一个异步的内存 + 磁盘图片缓存
支持 GIF
支持 WebP
后台图片解压缩处理
确保同一个 URL 的图片不被多次下载
确保虚假的 URL 不会被反复加载
确保下载及缓存时,主线程不被阻塞
使用 GCD 与 ARC
支持 Arm64
UIImageView+WebCache
首先,SDWebImage 最常见的使用场景想必如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #import <SDWebImage/UIImageView+WebCache.h> ... - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *MyIdentifier = @"MyIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease]; } // 在这里,我们使用 UIImageView 分类提供的 sd_setImageWithURL: 方法来加载网络图片 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; cell.textLabel.text = @"My Text"; return cell; } |
1 2 | [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; |
UIImageView+WebCache查看具体的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 202122 | /** * 根据 url、placeholder 与 custom options 为 imageview 设置 image * * 下载是异步的,并且被缓存的 * * @param url 网络图片的 url 地址 * @param placeholder 用于预显示的图片 * @param options 一些定制化选项 * @param progressBlock 下载时的 Block,其定义为:typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize); * @param completedBlock 下载完成时的 Block,其定义为:typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished); */ - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock { [self sd_cancelCurrentImageLoad]; objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); if (!(options & SDWebImageDelayPlaceholder)) { dispatch_main_async_safe(^{ self.image = placeholder; }); } if (url) { __weak __typeof(self)wself = self; id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (!wself) return; dispatch_main_sync_safe(^{ if (!wself) return; if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { wself.image = image; [wself setNeedsLayout]; } else { if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"]; } else { dispatch_main_async_safe(^{ NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; if (completedBlock) { completedBlock(nil, error, SDImageCacheTypeNone, url); } }); } } |
UIView+WebCacheOperation
首先来看:1 | [self sd_cancelCurrentImageLoad]; |
UIView+WebCacheOperation
1 23 | - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key { // 取消正在进行的下载队列 NSMutableDictionary *operationDictionary = [self operationDictionary]; id operations = [operationDictionary objectForKey:key]; if (operations) { if ([operations isKindOfClass:[NSArray class]]) { for (id <SDWebImageOperation> operation in operations) { if (operation) { [operation cancel]; } } } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ [(id<SDWebImageOperation>) operations cancel]; } [operationDictionary removeObjectForKey:key]; } } |
Associated Objects 的实现原理)添加到 UIView 上的一个属性,至于为什么添加到 UIView 上, 主要是因为这个 operationDictionary 需要在 UIButton 和 UIImageView 上重用,所以需要添加到它们的根类上。
当执行
sd_setImageWithURL:函数时,首先会 cancel 掉 operationDictionary 中已经存在的 operation,并重新创建一个新的 SDWebImageCombinedOperation 对象来获取 image,该 operation 会被存入 operationDictionary 中。
这样来保证每个 UIImageView 对象中永远只存在一个 operation,当前只允许一个图片网络请求,该 operation 负责从缓存中获取 image 或者是重新下载 image。
SDWebImageCombinedOperation的 cancel 操作同时会 cacel 掉缓存查询的 operation 以及 downloader 的 operation
dispatch_main_sync_safe & dispatch_main_async_safe 宏定义
再来看:1 23 | dispatch_main_async_safe(^{ self.image = placeholder; }); |
dispatch_main_sync_safe与
dispatch_main_async_safe均为宏定义, 点进去一看发现宏是这样定义的:
1 23 | #define dispatch_main_sync_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_sync(dispatch_get_main_queue(), block);\ } #define dispatch_main_async_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ } |
dispatch_main_sync_safe与
dispatch_main_async_safe就是为了保证 block 能在主线程中执行。
SDWebImageManager
在SDWebImageManager.h中你可以看到关于 SDWebImageManager 的描述:
The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). You can use this class directly to benefit from web image downloading
with caching in another context than a UIView.
这个类就是隐藏在
UIImageView+WebCache背后,用于处理异步下载和图片缓存的类,当然你也可以直接使用 SDWebImageManager 的上述方法
downloadImageWithURL:options:progress:completed:来直接下载图片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 202122 | /** * 如果在缓存中则直接返回,否则根据所给的 URL 下载图片 * * @param url 网络图片的 url 地址 * @param options 一些定制化选项 * @param progressBlock 下载时的 Block,其定义为:typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize); * @param completedBlock 下载完成时的 Block,其定义为:typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished); * @return 返回 SDWebImageOperation 的实例 */ - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock { /** * 前面省略 n 行,主要作了如下处理: * 1. 判断 url 的合法性 * 2. 创建 SDWebImageCombinedOperation 对象 * 3. 查看 url 是否是之前下载失败过的 * 4. 如果 url 为 nil,或者在不可重试的情况下是一个下载失败过的 url,则直接返回操作对象并调用完成回调 */ // 根据 URL 生成对应的 key,没有特殊处理为 [url absoluteString]; NSString *key = [self cacheKeyForURL:url]; // 去缓存中查找图片(参见 SDImageCache) operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) { /* ... */ // 如果在缓存中没有找到图片,或者采用的 SDWebImageRefreshCached 选项,则从网络下载 if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) { dispatch_main_sync_safe(^{ // 如果图片找到了,但是采用的 SDWebImageRefreshCached 选项,通知获取到了图片,并再次从网络下载,使 NSURLCache 重新刷新 completedBlock(image, nil, cacheType, YES, url); }); } /* 下载选项设置 */ // 使用 imageDownloader 开启网络下载 id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { /* ... */ if (downloadedImage && finished) { // 下载完成后,先将图片保存到缓存中,然后主线程返回 [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url); } }); } } /* ... */ } else if (image) { // 在缓存中找到图片了,直接返回 dispatch_main_sync_safe(^{ if (!weakOperation.isCancelled) { completedBlock(image, nil, cacheType, YES, url); } }); } }]; return operation; } |
要点
在 SDWebImageManager 中管理了一个 failedURLs 的 NSMutableSet,里面下载失败的 url 会被存储下来。同时,可以通过 SDWebImageRetryFailed 来强制继续重试下载查找缓存,若缓存中没有 image 则通过 SDWebImageDownloader 来进行下载,下载完成后通过 SDImageCache 进行缓存,会同时缓存到 memCache 和 diskCache 中
可以看到 SDWebImageManager 这个类的主要作用就是为 UIImageView+WebCache 和 SDWebImageDownloader,SDImageCache 之间构建一个桥梁,使它们能够更好的协同工作,在接下来的系列文章中,就让我们一探究竟:它是如何协调异步下载和图片缓存的?
原文:http://itangqi.me/2016/03/19/the-notes-of-learning-sdwebimage-one/
相关文章推荐
- SDWebImage 源码阅读笔记(三)
- SDWebImage 源码阅读笔记(四)
- SDWebImage 源码阅读笔记(二)
- [iOS]SDWebImage 源码阅读(三)下载
- SDWebImage 源码阅读(一)
- SDWebImage源码阅读-第三篇
- SDWebImage源码阅读-第一篇
- SDWebImage 源码阅读分享
- IOS SDWebImage 2.X源码阅读(三)
- 源码阅读:SDWebImage
- 【原】SDWebImage源码阅读(三)
- 【原】SDWebImage源码阅读(二)
- SDWebImage 源码阅读(三)
- [iOS]SDWebImage 源码阅读(一)
- SDWebImage 源码阅读(二)
- IOS SDWebImage 2.X源码阅读(四)
- SDWebImage源码阅读
- SDWebImage 源码阅读(四)
- [iOS]SDWebImage 源码阅读(二)缓存
- 【原】SDWebImage源码阅读(四)