1.工作流程
1.入口 setImageWithURL:placeholderImage:options: 会先把placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
2.进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 3.queryDiskCacheForKey:delegate:userInfo:.
先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4、SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
5、如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
6、根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
7、如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
8、如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
9、共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
10、图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
11、connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
12、图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
13、在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
14、通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
15、SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
16、SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
17、SDWebImagePrefetcher 可以预先下载图片,方便后续使用。
2.常见面试题目
(1)图片缓存在那个目录下?
缓存的方法在SDImageCache.m里面,图片默认缓存路径~/Library/Caches/default/com.hackemist.SDWebImageCache.default
(2)图片下载最大并发数和超时时间?
#import "SDWebImageDownloader.h"
//下载最大并发数
_downloadQueue.maxConcurrentOperationCount = 6;
//下载超时时间
_downloadTimeout = 15.0;
(3)图片是怎样命名的?
#import "SDImageCache.h"
//写入缓存用url做key
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
//写入磁盘需要将url进行md5作为图片的key,防止文件名称过长
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
return filename;
}
(4)如何识别图片类型?
#import "NSData+ImageContentType.h"
//通过NSData的第一个字符判断图片类型
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data ;
(5)查找到的图片可以知道图片来源吗?
typedef NS_ENUM(NSInteger, SDImageCacheType) { /**
* 从网上下载
*/ SDImageCacheTypeNone, /**
* 从磁盘获得
*/ SDImageCacheTypeDisk, /**
* 从内存获得
*/ SDImageCacheTypeMemory
};
(6)所有下载的图片都将被写入缓存?磁盘呢?何时缓存的?
//磁盘不是强制写入。从枚举SDWebImageOptions可见
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /**
* 禁用磁盘缓存
*/ SDWebImageCacheMemoryOnly = 1 << 2,
}
其一是下载成功后、自动保存。或者开发者通过代理处理图片并返回后缓存
其二是当缓存中没有、但是从硬盘中查询到了图片,在缓存上进行缓存。
(7)磁盘缓存的时长?清理操作的时间点?
默认一周清理一次
首先、通过时间进行清理。(最后修改时间>一周)
然后、根据占据内存大小进行清理。(如果占据内存大于上限、则按时间排序、删除到上限的1/2。)
(8)下载图片的URL必须是NSURL么?
不是,有容错处理。
(9)读取缓存以及读取磁盘的时候如何保证线程安全?
读取缓存
读取缓存的时候是在主线程进行。由于使用NSCache进行存储、所以不需要担心单个value对象的线程安全。读取磁盘
磁盘的读取虽然创建了一个NSOperation对象、但据我所见这个对象只是用来标记该操作是否被取消、以及取消之后不再读取磁盘文件的作用。
真正的磁盘缓存是在另一个IO专属线程中的一个串行队列下进行的。
如果你搜索self.ioQueue还能发现、不只是读取磁盘内容。
包括删除、写入等所有磁盘内容都是在这个IO线程进行、以保证线程安全。
但计算大小、获取文件总数等操作。则是在主线程进行。