AFAutoPurgingImageCache 源码阅读笔记
<code>UIKit+AFNetworking</code>
<code>AFAutoPurgingImageCache</code>
是一个在内存中管理图片缓存的工具,当存图片占内存的总数大于设置的最大size的时候,就会按照图片访问的时间,访问越早的就会被删除,直到占内存的总大小,小于预先设置的值。
看看实现过程
@protocol AFImageCache <NSObject>
协议定义了一组APT,用于同步的从缓存中 获取 删除,添加图片等方法。
@protocol AFImageRequestCache <AFImageCache>
基于 NSURLRequest 对 AFImageCache 进行了扩展。
看一下具体实现
@interface AFAutoPurgingImageCache : NSObject <AFImageRequestCache>
@property (nonatomic, assign) UInt64 memoryCapacity;
@property (nonatomic, assign) UInt64 preferredMemoryUsageAfterPurge;
@property (nonatomic, assign, readonly) UInt64 memoryUsage;
- (instancetype)init;
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;
@end
@interface AFAutoPurgingImageCache ()
@property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;
@property (nonatomic, assign) UInt64 currentMemoryUsage;
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
@end
从头文件和.m文件的类别中可以看到,缓存图片到内存中,使用的是 <code>NSMutableDictionary</code>, 并且声明了一个 同步队列 synchronizationQueue。
- (instancetype)init {
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;
}
初始化了保存图片的字典。和 同步队列,不过,队列初始化的时候使用的 DISPATCH_QUEUE_CONCURRENT,是一个并发队列。并且监听了一下内存不足的通知。
- (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;
}
内存不足的时候,删除,所有保存在字典中的图片。使用 dispatch_barrier_sync 栅栏进行同步操作,因为 self.synchronizationQueue 是一个并发队列。执行dispatch_barrier_sync 的时候要等队列中在执行的所有任务都执行完,才会进行清空的任务。
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
memoryUsage 函数返回当前已经使用的内存byte, 使用 dispatch_sync 进行同步操作。因为self.synchronizationQueue 是在本类中创建的,肯定不会存在死锁状态(这个方法是对外的,本类是不会调用的)。
- (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;
}
});
}
addImage 添加图片到缓存。使用dispatch_barrier_async 进行同步,dispatch_barrier_async 会等到队列中的所有任务都执行完才会执行提交进去的任务,不同于dispatch_barrier_sync ,dispatch_barrier_async 不会阻塞线程。
第一个block,主要作用是把 生成 AFCachedImage 对象,并且添加到字典中,AFCachedImage 是字典中保存的对象。
第二个 block 是检查,如果当前缓存中的byte大于配置的总数量,就删除一下很久不访问的图片资源。
- (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;
}
删除图片,从缓存,同样用了 dispatch_barrier_sync 进行同步。
小结
这个缓存类没有多大难度,主要适用了dispatch_barrier_XXX 函数进行同步操作,对缓存字典的操作都在同一个队列中,避免了锁的使用。
在AFNetworking 里的使用:
@interface AFImageDownloader : NSObject
@property (nonatomic, strong, nullable) id <AFImageRequestCache> imageCache;
AFImageDownloader 是AF提供的一个下载网络图片的组件。以后有空分析一下。。这个组件使用的缓存就是 AFAutoPurgingImageCache.
- (instancetype)init {
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
return [self initWithSessionManager:sessionManager
downloadPrioritization:AFImageDownloadPrioritizationFIFO
maximumActiveDownloads:4
imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
默认的初始化函数实现如上。。
在下载图片的时候会先判断一下本地有木有缓存,如:
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(nonnull NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
// 1) Append the success and failure blocks to a pre-existing request if it already exists
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
// 2) Attempt to load the image from the image cache if the cache policy allows it
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
直接看2)部分,判断缓存中有没有。如果缓存中有,直接调用成功的block.
还有下载成功之后,直接保存图片到缓存,代码如下:
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
完结
因为这块缓存使用的是接口的形式调用的。所以完全可以扩展一个自己的缓存,比如添加存到本地的功能。
<strong>因为直播首页的图片,间隔一分钟就会变动一次,每次打开APP的图片肯定不一样,所以,不用本地缓存应该比较好吧</strong>