AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache
这篇我们就要介绍AFAutoPurgingImageCache这个类了。这个类给了我们临时管理图片内存的能力。
前言
假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来管理应用内的所有的下载事件。至于下载器能够提供的功能,在此先不做说明。但在 AFAutoPurgingImageCache 中我们能够借鉴一些东西。
AFImageCache
通过这个协议,我们能够做下边四件事:
AFImageRequestCache
这个协议继承自AFImageCache,然后又扩展了下边三个方法:
AFAutoPurgingImageCache
它集成了AFImageRequestCache协议,因此上图中的方法都会实现。我们先看看它暴露出来的有哪些东西:
UInt64 memoryCapacity
总共的内存容量UInt64 preferredMemoryUsageAfterPurge
当清空时优先保存的容量UInt64 memoryUsage
当前已使用的容量init
初始化方法initWithMemoryCapacity: preferredMemoryCapacity:
初始化方法
AFCachedImage
AFCachedImage用来抽象被缓存的图片,看到这个对象,我联想到一个下载器也需要一个这样的被下载的对象的抽象描述类。我们需要一些属性来描述这个被缓存的图片。
UIImage *image
图片NSString *identifier
标识UInt64 totalBytes
总大小,已字节为单位NSDate *lastAccessDate
最后的访问时间,用于清理内存时,进行排序UInt64 currentMemoryUsage
当前的容量使用情况
--
-(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
if (self = [self init]) {
self.image = image;
self.identifier = identifier;
// 去的图片的尺寸
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
// 每个像素占用4个字节
CGFloat bytesPerPixel = 4.0;
//这个是指图片中有多少个像素,这个名称bytesPerSize改为pixelsPerSize是不是更加贴切呢?
CGFloat bytesPerSize = imageSize.width * imageSize.height;
self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
self.lastAccessDate = [NSDate date];
}
return self;
}
--
- (UIImage*)accessImage {
self.lastAccessDate = [NSDate date];
return self.image;
}
- (NSString *)description {
NSString *descriptionString = [NSString stringWithFormat:@"Idenfitier: %@ lastAccessDate: %@ ", self.identifier, self.lastAccessDate];
return descriptionString;
}
AFAutoPurgingImageCache实现部分
既然是图片的临时缓存类,那么我们应该把图片缓存到什么地方呢?答案就是一个字典中。值得注意的是,我们缓存使用的是一个同步的队列 。
NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages
存放图片UInt64 currentMemoryUsage
当前使用的容量dispatch_queue_t synchronizationQueue
队列
--
- (instancetype)init {
return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}
通过这个方法,我们就能够看出默认的缓存容量的大小为100M,清除后保存容量为60M。
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
if (self = [super init]) {
self.memoryCapacity = memoryCapacity;
self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
self.cachedImages = [[NSMutableDictionary alloc] init];
NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(removeAllImages)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
--
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
--
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
dispatch_barrier_async(self.synchronizationQueue, ^{
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
if (previousCachedImage != nil) {
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
self.cachedImages[identifier] = cacheImage;
self.currentMemoryUsage += cacheImage.totalBytes;
});
dispatch_barrier_async(self.synchronizationQueue, ^{
if (self.currentMemoryUsage > self.memoryCapacity) {
UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
ascending:YES];
[sortedImages sortUsingDescriptors:@[sortDescriptor]];
UInt64 bytesPurged = 0;
for (AFCachedImage *cachedImage in sortedImages) {
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
if (bytesPurged >= bytesToPurge) {
break ;
}
}
self.currentMemoryUsage -= bytesPurged;
}
});
}
这个方法是核心方法,我们重点介绍下,在这个方法中,一共做了两件事:
- 把图片加入到缓存字典中(注意字典中可能存在identifier的情况),然后计算当前的容量大小
- 处理容量超过最大容量的异常情况。分为下边几个步骤: 1.比较容量是否超过最大容量 2.计算将要清楚的缓存容量 3.把所有缓存的图片放到一个数组中 4.对这个数组按照最后访问时间进行排序,优先保留最后访问的数据 5.遍历数组,移除图片(当已经移除的数据大于应该移除的数据时停止)
ps: 这里不得不讲一下 dispatch_barrier_async 这个方法。barrier 这个单词的意思是障碍,拦截的意思,也即是说dispatch_barrier_async一定是有拦截事件的作用。
看下边这段代码:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-1");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-2");
});
dispatch_barrier_async(concurrentQueue, ^(){
NSLog(@"dispatch-barrier");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-3");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-4");
});
打印结果:
2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1
2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3
2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
这个说明了dispatch_barrier_async能够拦截它前边的异步事件,等待两个异步方法都完成之后,调用dispatch_barrier_async。
我们稍微改动一下:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-1");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-2");
});
dispatch_barrier_sync(concurrentQueue, ^(){
NSLog(@"dispatch-barrier");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-3");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-4");
});
打印结果:
2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1
2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3
2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
--
- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
NSString *key = request.URL.absoluteString;
if (additionalIdentifier != nil) {
key = [key stringByAppendingString:additionalIdentifier];
}
return key;
}
通过这个方法可以看出,使用NSURLRequest进行缓存的时候,也只是使用了request.URL.absoluteString
+ additionalIdentifier
来作为缓存字典的key。在这里其他协议的实现方法就不做介绍了。
总结
通过这个文件,提供给了我们一个关于下载器 下载后的文件的一个封装的思路。按照正常来说,下载后的文件的标识应该就是URL。
推荐阅读
AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy
AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache的更多相关文章
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...
- AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
- AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...
- AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
本篇是AFNetworking 3.0 源码解读的第五篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...
随机推荐
- GreenDao 数据库:使用Raw文件夹下的数据库文件以及数据库升级
一.使用Raw文件夹下的数据库文件 在使用GreenDao框架时,数据库和数据表都是根据生成的框架代码来自动创建的,从生成的DaoMaster中的OpenHelper类可以看出: public sta ...
- 一个表缺失索引发的CPU资源瓶颈案例
背景 近几日,公司的应用团队反应业务系统突然变慢了,之前是一直比较正常.后与业务部门沟通了解详情,得知最近生意比较好,同时也在做大的促销活动,使得业务数据处理的量出现较大的增长,最终系统在处理时出现瓶 ...
- SQL Server2016升级前几点自检
SQL Server2016已经出来一段时间了,而且最新的SP1包也于2016年11月18日正式发布,各种新的特性推出让我们跃跃欲试.那么对于我们真实的业务环境,特别是生产环境要不要"跟风& ...
- 如何安全的将VMware vCenter Server使用的SQL Server Express数据库平滑升级到完整版
背景: 由于建设初期使用的vSphere vCenter for Windows版,其中安装自动化过程中会使用SQL Server Express的免费版数据库进行基础环境构建.而此时随着业务量的增加 ...
- 用WebRequest +HtmlAgilityPack 从外网抓取数据到本地
相信大家对于WebRequest 并不陌生,我们在C#中发请求的方式,就是创建一个WebRequest .那么如果我们想发一个请求到外网,比如国内上不了的一些网站,那么该怎么做呢? 其实WebRequ ...
- 【干货分享】流程DEMO-外出申请
流程名: 外出申请 流程相关文件: 流程包.xml 流程说明: 直接导入流程包文件,即可使用本流程 表单: 流程: 图片:2.png DEMO包下载: http://files.cnblog ...
- 如何使用本地账户"完整"安装 SharePoint Server 2010+解决“New-SPConfigurationDatabase : 无法连接到 SharePoint_Config 的 SQL Server 的数据 库 master。此数据库可能不存在,或当前用户没有连接权限。”
注:目前看到的解决本地账户完整安装SharePoint Server 2010的解决方案如下,但是,有但是的哦: 当我们选择了"完整"模式安装SharePointServer201 ...
- Angular2 Hello World 之 RC6
angular2还没有发布正式版,确实有点不靠谱,变化太频繁,之前写的demo直接将js升级到最新版之后发现就不能用了……所以现在在写一篇demo——基于RC6.参考:http://web3.code ...
- 笔记:Memory Notification: Library Cache Object loaded into SGA
笔记:Memory Notification: Library Cache Object loaded into SGA在警告日志中发现一些这样的警告信息:Mon Nov 21 14:24:22 20 ...
- SQL Server事务、视图和索引
废话不多说,直接上干货 14:13:23 事务 概括:事务是一种机制,一个操作序列,包含一组数据库操作命令,并且把所有的命令作为一个整体一起 向系统提交或撤销操作 请求. 事务的特性: 1.原子性 ...