AFN AFAutoPurgingImageCache 源码分析
其实这个源码特别简单,但是却可以学习人家的思想,在我们平时开发中,使用 image 的频率是非常非常大的,但如果我们经常调用 imageNamed这个系统 api 去加载本地图片的时候,是会耗时额,imageNamed 主要做两件事,第一 I/O 操作,首次加载图片是需要去磁盘中的,所以有 I/O 操作,然后对图片进行解码,将位图数据还原为原始像素数据,这里加载图片就先不说 SD 和 YY 加载图片的技术了,这里 AFN 用的是一个内存缓存的一个思路,在平时开发中也可以应用
AFCachedImage
图片的缓存类,相当于映射系统的 UIImage , 但是比 UIImage 多了几个属性,如当前图片的大小,因为要放在缓存中,所以需要计算当前图片占用的大小,然后是图片的访问时间,因为会限定缓存图片需要占用的总大小,所以,如果超出限制需要淘汰,就是根据这个访问时间淘汰的,这就是这个类的作用
AFAutoPurgingImageCache
- (instancetype)init {
// 默认缓存容量为100M,清除后保留60M容量
return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}
- (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;
}
初始化缓存类,主要初始化,缓存允许的总大小,如超出总大小之后,需要留下多少缓存,比如我们允许缓存中总共存储100M的图片,如果超出一百兆,我们清除40M,留下60M。
- (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;
}
});
}
添加图片,这里使用的是栅栏函数,主要就是多读单写,这里添加图片,相当于写,单写的,每次添加图片额时候,根据ID判断缓存中是否有缓存,如果有,那么就先将当前总共占用额缓存减去已经存在的ID对应额那个图片额缓存,然后将新的ID图片添加到缓存,然后当前总缓存加上新的图片额缓存,接下来,判断当前的总缓存,是不是超过我们预定号的总额memoryCapacity缓存,如果超过,就遍历所有图片,根据访问时间进行淘汰,直到剩余preferredMemoryUsageAfterPurge这么大为止,可以比这个值小,那么就符合要求了
我们再来看取缓存大小
// 多读单写
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
可以看到是同步多读的,,利用栅栏函数实现多读单写
- (BOOL)removeImageWithIdentifier:(NSString *)identifier {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
if (cachedImage != nil) {
[self.cachedImages removeObjectForKey:identifier];
self.currentMemoryUsage -= cachedImage.totalBytes;
removed = YES;
}
});
return removed;
}
- (BOOL)removeAllImages {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
if (self.cachedImages.count > 0) {
[self.cachedImages removeAllObjects];
self.currentMemoryUsage = 0;
removed = YES;
}
});
return removed;
}
删除也是个互斥单操作
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
image = [cachedImage accessImage];
});
return image;
}
从缓存中读取图片,是个同步多读的操作,这就是栅栏函数的魅力。
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
[self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
NSString *key = request.URL.absoluteString;
if (additionalIdentifier != nil) {
key = [key stringByAppendingString:additionalIdentifier];
}
return key;
}
这里主要是根据 URL 来缓存图片,根据请求的url和一个ID,来唯一确定一个ID,来缓存图片
网友评论