这篇我们就要介绍AFAutoPurgingImageCache这个类了。这个类给了我们临时管理图片内存的能力。

前言

假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来管理应用内的所有的下载事件。至于下载器能够提供的功能,在此先不做说明。但在 AFAutoPurgingImageCache 中我们能够借鉴一些东西。

AFImageCache

通过这个协议,我们能够做下边四件事:

AFImageRequestCache

这个协议继承自AFImageCache,然后又扩展了下边三个方法:

AFAutoPurgingImageCache

它集成了AFImageRequestCache协议,因此上图中的方法都会实现。我们先看看它暴露出来的有哪些东西:

  1. UInt64 memoryCapacity 总共的内存容量
  2. UInt64 preferredMemoryUsageAfterPurge 当清空时优先保存的容量
  3. UInt64 memoryUsage 当前已使用的容量
  4. init 初始化方法
  5. initWithMemoryCapacity: preferredMemoryCapacity: 初始化方法

AFCachedImage

AFCachedImage用来抽象被缓存的图片,看到这个对象,我联想到一个下载器也需要一个这样的被下载的对象的抽象描述类。我们需要一些属性来描述这个被缓存的图片。

  1. UIImage *image 图片
  2. NSString *identifier 标识
  3. UInt64 totalBytes 总大小,已字节为单位
  4. NSDate *lastAccessDate 最后的访问时间,用于清理内存时,进行排序
  5. 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实现部分

既然是图片的临时缓存类,那么我们应该把图片缓存到什么地方呢?答案就是一个字典中。值得注意的是,我们缓存使用的是一个同步的队列

  1. NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages 存放图片
  2. UInt64 currentMemoryUsage 当前使用的容量
  3. 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;
}
});
}

这个方法是核心方法,我们重点介绍下,在这个方法中,一共做了两件事:

  1. 把图片加入到缓存字典中(注意字典中可能存在identifier的情况),然后计算当前的容量大小
  2. 处理容量超过最大容量的异常情况。分为下边几个步骤: 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的更多相关文章

  1. AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

    AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...

  2. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  3. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  4. AFNetworking 3.0 源码解读(八)之 AFImageDownloader

    AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...

  5. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  6. AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

    AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...

  7. AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

    这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...

  8. AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

    本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...

  9. AFNetworking 3.0 源码解读(五)之 AFURLSessionManager

    本篇是AFNetworking 3.0 源码解读的第五篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...

随机推荐

  1. Jquery的点击事件,三句代码完成全选事件

    先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...

  2. C++中的引用

    一,C++中引用的基础知识 1.引用的基本概念 1.所谓的引用其实就是对变量起“别名”.引用和变量对应得是相同的内存,修改引用的值,变量的值也会改变,和指针类似. 2.引用在定义的时候必须要初始化,初 ...

  3. 前端制作动画的几种方式(css3,js)

    制作动态的网页是是前端工程师必备的技能,很好的实现动画能够极大的提高用户体验,增强交互效果,那么动画有多少实现方式,一直对此有选择恐惧症的我就总结一下,以便在开发的时候选择最好的实现方式. 1.css ...

  4. 如果你也会C#,那不妨了解下F#(7):面向对象编程之继承、接口和泛型

    前言 面向对象三大基本特性:封装.继承.多态.上一篇中介绍了类的定义,下面就了解下F#中继承和多态的使用吧.

  5. 那些年【深入.NET平台和C#编程】

    一.深入.NET框架 1..NET框架具有两个组件:CLR(公共语言运行时)和FCL(框架类库),CLR是.NET框架的基础 2.框架核心类库: System.Collections.Generic: ...

  6. 原生JS实现-星级评分系统

    今天我又写了个很酷的实例:星级评分系统(可自定义星星个数.显示信息) sufuStar.star();使用默认值5个星星,默认信息 var msg = [........]; sufuStar.sta ...

  7. Maven安装

    开发分布式的商场系统,用到了一些新的技术,做一个记录和分享 这里讲一下maven安装 首先什么是Maven Maven作为一个构建工具,不仅帮我们自动化构建,还能抽象构建过程,提供构建任务实现.他跨平 ...

  8. wireshark 相关提示

    Packet size limited during capture      提示说明标记的包没有抓全,在某些操作系统中,默认只抓96个字节,tcpdump中有"-s"参数可用于 ...

  9. 超千个节点OpenStack私有云案例(1):CERN 5000+ 计算节点私有云

    CERN:欧洲核子研究组织 本文根据以下几篇文章整理而来: https://www.openstack.org/summit/tokyo-2015/videos/presentation/unveil ...

  10. babel-loader-presets

    babel-loader的presets的设置有一定的顺序.es2015必须出现在stage-0前面,我记得这是因为es2015是ES6的标准,state-0等是对ES7一些提案的支持, state- ...