用于缓存图片的类
通过identifier来添加和搜索UIImage
AFImageCache协议:
定义协议AFImageCache继承自协议NSObject,也就是说只有NSObject的子类才能实现该协议。
协议中添加图片的方法:
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;
协议中删除图片的方法:
- (BOOL)removeImageWithIdentifier:(NSString *)identifier;
协议中删除所有图片的方法:
- (BOOL)removeAllImages;
最重要的,通过identifier获取图片的方法:
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;
AFImageRequestCache协议:
协议AFImageRequestCache继承自协议AFImageCache,添加了通过NSURLRequest来添加,删除,获取图片的方法:
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
AFAutoPurgingImageCache(自动清理图片缓存类):
继承自NSObject,并实现AFImageRequestCache协议,只实现了内存缓存,并定义了最大缓存:@property (nonatomic, assign) UInt64 memoryCapacity;,当达到最大缓存时要清除一部分图片,直到缓存小于@property (nonatomic, assign) UInt64 preferredMemoryUsageAfterPurge;
,可以通过- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;来初始化AFAutoPurgingImageCache来指定缓存最大值和自动清理缓存时要达到的值。也可以通过- (instancetype)init;直接初始化,它会返回最大缓存100MB,自动清理到60MB的AFAutoPurgingImageCache实例。AFAutoPurgingImageCache对外提供@property (nonatomic, assign, readonly) UInt64 memoryUsage;,用来得到当前已缓存大小。
先要了解AFCachedImage
AFAutoPurgingImageCache的缓存数据用字典来表示@property (nonatomic, strong) NSMutableDictionary *cachedImages;,其中AFCachedImage类封装了UIImage对象@property (nonatomic, strong) UIImage *image;,以及该UIImage对象的内存大小@property (nonatomic, assign) UInt64 totalBytes;,和最后获取该缓存图片的时间@property (nonatomic, strong) NSDate *lastAccessDate;,只能通过它提供的- (UIImage*)accessImage这个方法来获取缓存中的图片,这样就可以在每次获取缓存图片时更新最后获取的时间:
- (UIImage*)accessImage {
self.lastAccessDate = [NSDate date];
return self.image;
}
AFCachedImage通过图片和identifier来初始化:
-(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);
CGFloat bytesPerPixel = 4.0;
CGFloat bytesPerSize = imageSize.width * imageSize.height;
self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
self.lastAccessDate = [NSDate date];
}
return self;
}
先想一个问题“一个图像的尺寸到底是多大呢?”
第一反应可能就是image.size,恭喜你答错了,正确的答案是图像的实际的尺寸(像 素)等于image.size乘以image.scale。如果做过界面贴图的话你可能经常会需要准备至少两套图,一套1倍图,一套图已@2x命名的二倍 图。这样当我们的程序运行在retina屏幕的时候系统就会自动的去加载@2x的图片,它的size将和一倍图加载进来的size相等,但是scale却 置为2,这点大家可以做个简单的小测试验证一下。然我们再深入一点儿为什么不直接加载到成二倍的尺寸呢,原因很简单因为我们在界面布局中逻辑坐标系中的 (单位是point),而实际的绘制都是在设备坐标系(单位是pixel)进行的,系统会自动帮我们完成从point到pixel之间的转化。其实这个比 例也就刚好和UIScreen中的scale对应,这样整条scale的线就可以串通了。
再返回AFAutoPurgingImageCache
该类的匿名扩展中定义了私有属性@property (nonatomic, assign) UInt64 currentMemoryUsage;,用来管理当前使用的内存大小。以及同步队列@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;,用来操作cachedImages的读写,以及修改后会影响的属性(currentMemoryUsage)的读取,synchronizationQueue在init中被定义为并行队列:
NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
因为对读操作是高并发的,无需等待队列中的上一个读操作执行完才可以执行下一个读操作。对于cachedImages的写,使用的是dispatch_barrier_async(self.synchronizationQueue, ^{...});,这个的意思就是在放入写操作的时候要等待之前已放入队列的读的所有操作都执行完才能执行写,在写的操作没有执行完时是无法执行后面放入的读操作的。这样就解决了多线程读写同一属性造成的数据错误。
读:
获取当前已经使用的内存大小:
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
通过identifier获取UIimage:
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
image = [cachedImage accessImage];
});
return image;
}
写操作,同时也是AFImageCache协议的实现:
清除所有缓存:
- (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;
}
清除指定identifier的图片缓存:
- (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;
}
添加缓存图片,并提供identifier,添加缓存图片分两步操作,第一步是添加缓存,第二步是查看当前使用的缓存内存是否超过memoryCapacity,如果超过,则要清理缓存直到缓存内存小于preferredMemoryUsageAfterPurge:
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
//第一步:
dispatch_barrier_async(self.synchronizationQueue, ^{
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
//如果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 *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;
}
});
}
AFURLImageCache协议的实现:
- (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;
}
最后:
写缓存类一定要监听内存警告的notification,监听到这个消息后要清除所有缓存。
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(removeAllImages)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
并在delloc中移除消息监听:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}