SDWebImageUML图分析
- SDWebImageManager是管理的核心类,他聚合了SDWebImageManagerdelegate
- SDWebImagePrefetcher是预下载的,SDWebImageManager作为一个变量manager聚合在SDWebImagePrefetcher,并且聚合了变量为delegate的SDWebImagePrefetcherDelegate
- UIButton+WebCache、UIImageView+WebCache(图的左边)等category是依赖uiview+webCache,uiview+webCache又是依赖于SDWebImageManager
- SDImageCache和SDWebImageDownLoader作为属性聚合在manager中
SDWebImage时序图分析
核心类说明
SDWebImageManager
SDWebImageManager的SDWebImageOptions
我们在用SDWebImage都会用到SDWebImageOptions,下面说下他的含义
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
* 默认情况下,当一个 URL 下载失败,该URL被列入黑名单,将不会继续尝试下载
* This flag disable this blacklisting.
* 此标志取消黑名单
*/
SDWebImageRetryFailed = 1 << 0, // 下载失败后还会重新去重试
/**
* By default, image downloads are started during UI interactions, this flags disable this feature,
* 默认情况下,在 UI 交互时也会启动图像下载,此标记取消这一功能
* leading to delayed download on UIScrollView deceleration for instance.
* 会延迟下载,UIScrollView停止滚动之后再继续下载
* 下载事件监听的运行循环模式是 NSDefaultRunLoopMode
*/
SDWebImageLowPriority = 1 << 1,
/**
* This flag disables on-disk caching
* 禁用磁盘缓存
*/
SDWebImageCacheMemoryOnly = 1 << 2,
/**
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
* 此标记允许渐进式下载,就像浏览器中那样,下载过程中,图像会逐步显示出来
* By default, the image is only displayed once completely downloaded.
* 默认情况下,图像只会在下载完后显示
*/
SDWebImageProgressiveDownload = 1 << 3,
/**
* Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
* 即使图像被缓存,遵守 HTPP 响应的缓存控制,如果需要,从远程刷新图像
* The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
* 磁盘缓存将由 NSURLCache 处理,而不是 SDWebImage,这会对性能有轻微的影响
* This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
* 此选项有助于处理同一个请求 URL 的图像发生变化
* If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
* 如果缓存的图像被刷新,会调用一次 completion block,并传递最终的图像
*
* Use this flag only if you can't make your URLs static with embedded cache busting parameter.
* 仅在无法使用嵌入式缓存清理参数确定图像 URL 时,使用此标记
*/
SDWebImageRefreshCached = 1 << 4,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* 在 iOS 4+,当 App 进入后台后仍然会继续下载图像。这是向系统请求额外的后台时间以保证下载请求完成的
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
* 如果后台任务过期,请求将会被取消
*/
SDWebImageContinueInBackground = 1 << 5, // 后台下载
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* 通过设置
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
* 处理保存在 NSHTTPCookieStore 中的 cookies
*/
SDWebImageHandleCookies = 1 << 6, // 是否管理cookies
/**
* Enable to allow untrusted SSL certificates.
* 允许不信任的 SSL 证书
* Useful for testing purposes. Use with caution in production.
* 可以出于测试目的使用,在正式产品中慎用
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/**
* By default, images are loaded in the order in which they were queued. This flag moves them to
* 默认情况下,图像会按照在队列中的顺序被加载,此标记会将它们移动到队列前部立即被加载
* the front of the queue.
*/
SDWebImageHighPriority = 1 << 8,
/**
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* 默认情况下,在加载图像时,占位图像已经会被加载。而此标记会延迟加载占位图像,直到图像已经完成加载
* of the placeholder image until after the image has finished loading.
*/
SDWebImageDelayPlaceholder = 1 << 9, // 延迟占位图
/**
* We usually don't call transformDownloadedImage delegate method on animated images,
* 通常不会在可动画的图像上调用 transformDownloadedImage 代理方法,因为大多数转换代码会破坏动画文件
* as most transformation code would mangle it.
* Use this flag to transform them anyway.
* 使用此标记尝试转换
*/
SDWebImageTransformAnimatedImage = 1 << 10, // 改变动画图像
/**
* By default, image is added to the imageView after download. But in some cases, we want to
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success
*/
SDWebImageAvoidAutoSetImage = 1 << 11 // 避免自动设置图像
};
SDImageCache
SDImageCache是sdWebImage的缓存处理
_maxCacheAge = kDefaultCacheMaxCacheAge; // 7天
SDImageCache默认的最大缓存时间是7天
// Init the memory cache 自动清除缓存
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
// 清空内存
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
// 清理磁盘
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification
object:nil];
// 后台清理磁盘
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
自动清除缓存
通过查看AutoPurgeCache可知,他是在发现UIApplicationDidReceiveMemoryWarningNotification即内存警告的时候,全部把缓存清除
另外程序终止的时候,清理磁盘。程序进入后台的时候,执行后台清理磁盘
清理缓存,就是把系统的缓存
- (void)clearMemory {
[self.memCache removeAllObjects];
}
清理磁盘
清理磁盘的步骤
- 先清除已超过的最大缓存时间
- 第一轮会保留文件属性
- 清除之后,如果设置了最大的缓存, 保留之前的文件,先删除最老的文件
- 删除最老的文件, 到达它最大缓存的一半
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
// 1.找到磁盘缓存目录
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
// 2.创建了一个数组,放入了key
/*
NSURLIsDirectoryKey, 目录key
NSURLContentModificationDateKey, 最后修改的时间key
NSURLTotalFileAllocatedSizeKey 文件总大小的一个key
*/
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
// 3.枚举器(迭代器的模式),类似于数组.
// NSDirectoryEnumerationSkipsHiddenFiles 忽略隐藏文件
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
// 4. 从先现在开始,减去最大缓存时间
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
// 5.定义字典
NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
// 6.定义当前缓存大小
NSUInteger currentCacheSize = 0;
// Enumerate all of the files in the cache directory. This loop has two purposes:
//
// 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass.
// 7.创建了一个可变数组
NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
// 8.使用for拿到了具体的URL
/*
1.清除了超过日期的文件
2.以大小为基础的第二轮清除
*/
for (NSURL *fileURL in fileEnumerator) {
// 9. 访问单独的URL, 获取指定key的信息
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
// Skip directories.
// 10. 判断是目录,就直接跳过
if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}
// Remove files that are older than the expiration date;
// 11. 拿到了文件的修改日期
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
// 12.如果是最后的修改日期, 添加到数组
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}
// Store a reference to this file and account for its total size.
// 13.对没有过期的文件,用键值的方式添加到字典
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
[cacheFiles setObject:resourceValues forKey:fileURL];
}
// 14.删除过期的文件
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}
// If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
// 15. 设置了缓存上限,才会走if分支
if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
// 16. 所需要的缓存大小的一半
const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first).
// 17.最早进来的排前面
NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// Delete files until we fall below our desired cache size.
// 18.删除文件,到低于缓存大小
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
PS NSCache
NSCache是系统提供的一种类似于集合(NSMutableDictionary)的缓存,它与集合的不同如下:
NSCache具有自动删除的功能,以减少系统占用的内存;
NSCache是线程安全的,不需要加线程锁;
键对象不会像 NSMutableDictionary 中那样被复制。(键不需要实现 NSCopying 协议)。
属性
@property NSUInteger totalCostLimit;
设置缓存占用的内存大小,并不是一个严格的限制,当总数超过了totalCostLimit设定的值,系统会清除一部分缓存,直至总消耗低于totalCostLimit的值。
@property NSUInteger countLimit;
设置缓存对象的大小,这也不是一个严格的限制。
- (id)objectForKey:(id)key;
获取缓存对象,基于key-value对
- (void)setObject:(id)obj forKey:(id)key; // 0 cost
存储缓存对象,考虑缓存的限制属性;
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g;
存储缓存对象,cost是提前知道该缓存对象占用的字节数,也会考虑缓存的限制属性,建议直接使用 - (void)setObject:(id)obj forKey:(id)key;
NSCacheDelegate代理
代理属性声明如下:
@property (assign) id<NSCacheDelegate>delegate;
实现了NSCacheDelegate代理的对象,在缓存对象即将被清理的时候,系统回调代理方法如下:
-(void)cache:(NSCache *)cache willEvictObject:(id)obj;
第一个参数是当前缓存(NSCache),不要修改该对象;
第二个参数是当前将要被清理的对象,如果需要存储该对象,可以在此操作(存入Sqlite or CoreData);
该代理方法的调用会在缓存对象即将被清理的时候调用,如下场景会调用:
-(void)removeObjectForKey:(id)key; 手动删除对象;
缓存对象超过了NSCache的属性限制;(countLimit 和 totalCostLimit )
App进入后台会调用;
系统发出内存警告;