图片缓存框架
图片的读写
-
读取图片
-
缓存图片
(1)如果程序并没有被关闭,一直在运行,那么此时内存缓存中有数据,硬盘缓存中有数据。如果此时再次请求数据,直接使用内存缓存中的数据即可
(2)如果程序重新启动,那么此时内存缓存已经消失,没有数据,硬盘缓存依旧存在,还有数据。如果此时再次请求数据,需要读取内存中缓存的数据。
Manager
是一个单例,用来暴露出缓存框架的接口,主要的接口有:
- 根据传入图片的URL来获取图片的接口
- 设置内存缓存阀值大小的接口
- 设置磁盘缓存阀值大小的接口
- 主动释放磁盘缓存的接口
暂时不支持: 主要是实时下载图片的时候线程的一些状态的回调和设置
- 返回图片的下载进度的回调
- 主动取消图片下载的方法等
内存缓存
使用NSCache
NSCache的使用很方便,提供了类似可变字典的使用方式,但它比可变字典更适用于实现缓存,最重要的原因为NSCache是线程安全的,使用NSMutableDictionary自定义实现缓存时需要考虑加锁和释放锁,NSCache已经帮我们做好了这一步。其次,在内存不足时NSCache会自动释放存储的对象,不需要手动干预,如果是自定义实现需要监听内存状态然后做进一步的删除对象的操作
模拟器收到系统的内存警告,NSCache并不会清理,这里是需要主动注册一个内存警告用于清理内存缓存:
- 一个继承自NSCache的新类,重写了init和dealloc方法,在这两个方法中进行了添加和删除系统内存警告通知的监听操作,目的就是为了在收到系统内存警告后进行内存的清理工作。 **
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
//收到系统内存警告后直接调用 removeAllObjects 删除所有缓存对象
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@end
磁盘缓存
-
存储方式选择
iOS的沙盒机制
Documents:保存应用运行时生成的需要持久化的数据,iTunes会自动备份该目录。苹果建议将在应用程序中浏览到的文件数据保存在该目录下。
tmp:临时文件目录,在程序重新运行的时候,和开机的时候,会清空tmp文件夹。
Library:
- Caches:
一般存储的是缓存文件,例如图片视频等,此目录下的文件不会再应用程序退出时删除。
在手机备份的时候,iTunes不会备份该目录。
例如音频,视频等文件存放其中 - Preferences:
保存应用程序的所有偏好设置iOS的Settings(设置),我们不应该直接在这里创建文件,
而是需要通过NSUserDefault这个类来访问应用程序的偏好设置。
iTunes会自动备份该文件目录下的内容。
比如说:是否允许访问图片,是否允许访问地理位置......
-
大小限制
NSFileManager -
淘汰策略
日期和大小
网络请求
NSOperation的串并行是通过设置最大并发数maxConcurrentOperationCount来实现的。
当maxConcurrentOperationCount为1时,就是串行执行
当maxConcurrentOperationCount > 1时,就是并行执行
图片解码解压缩
图片加载的工作流
概括来说,从磁盘中加载一张图片,并将它显示到屏幕上,主要工作流如下:
- 假设我们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,这个时候的图片并没有解压缩;
- 然后将生成的 UIImage 赋值给 UIImageView ;
- 接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化;
- 在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操作,而受图片是否字节对齐等因素的影响,这个 copy 操作可能会涉及以下部分或全部步骤:
a.分配内存缓冲区用于管理文件 IO 和解压缩操作;
b.将文件数据从磁盘读到内存中;
c.将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;
d.最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层。
在上面的步骤中,我们提到了图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出。
在将磁盘中的图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这就是为什么需要对图片解压缩的原因。
解决方案:
当未解压缩的图片将要渲染到屏幕时,系统会在主线程对图片进行解压缩,而如果图片已经解压缩了,系统就不会再对图片进行解压缩。因此,也就有了业内的解决方案,在子线程提前对图片进行强制解压缩。
而强制解压缩的原理就是对图片进行重新绘制,得到一张新的解压缩后的位图。其中,用到的最核心的函数是CGBitmapContextCreate
图片解码的时机:
- 在 子线程 图片刚下载完时
- 在 子线程 刚从磁盘读取完时
避免在主线程解压缩、解码,避免卡顿