AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

时间:2024-01-16 16:29:20

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

前言

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

AFImageCache

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

AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

AFImageRequestCache

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

AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

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