之前在UIImageView提到,在開啟圖片下載任務(wù)之前,會(huì)先查找緩存中是否有該圖片。
//5、如果圖片緩存存在秕重,就使用該緩存圖片 Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
查找緩存圖片的這個(gè)方法- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier 來自AFAutoPurgingImageCache這個(gè)類。
在AFAutoPurgingImageCache的.h文件中申明了協(xié)議AFImageCache厉膀。
/**
The `AFImageCache` protocol defines a set of APIs for adding, removing and fetching images from a cache synchronously.
*/
AFImageCache 這個(gè)協(xié)議有以下4個(gè)功能溶耘。
還有一個(gè)協(xié)議AFImageRequestCache,它繼承自AFImageCache服鹅,又?jǐn)U展了3個(gè)方法凳兵。
在AFAutoPurgingImageCache的.m文件中申明了AFCachedImage。
@interface AFCachedImage : NSObject
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) NSString *identifier; //圖片標(biāo)識(shí)符
@property (nonatomic, assign) UInt64 totalBytes;//圖片總大小
@property (nonatomic, strong) NSDate *lastAccessDate;//最近訪問時(shí)間
@property (nonatomic, assign) UInt64 currentMemoryUsage; // 當(dāng)前內(nèi)存容量
@end
而AFAutoPurgingImageCache的屬性中企软,有一個(gè)存放AFCachedImage對(duì)象的字典cachedImages庐扫。
注意一下,在AFNetworking中仗哨,緩存的圖片是放在NSMutableDictionary的可變字典中的形庭,不像SDWebImage那樣,有做2層緩存處理厌漂。
@interface AFAutoPurgingImageCache ()
@property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;
@property (nonatomic, assign) UInt64 currentMemoryUsage;
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
@end
了解了AFAutoPurgingImageCache的基本信息萨醒,下面倒著來看下這個(gè)緩存的方法。
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
這個(gè)方法苇倡,又是通過圖片的identifier來尋找的富纸。
再看這個(gè)imageWithIdentifier方法
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
image = [cachedImage accessImage];
});
return image;
}
在 并行隊(duì)列 synchronizationQueue中,讀取圖片旨椒。
NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
此時(shí)晓褪,cachedImages這個(gè)字典派上用場(chǎng)了,通過identifier這個(gè)key综慎,尋找對(duì)應(yīng)的AFCachedImage類型的緩存圖片辞州。然后通過AFCachedImage類中的accessImage方法,先設(shè)置lastAccessDate最新訪問的時(shí)間為最新時(shí)間寥粹,再將屬性中存儲(chǔ)的image返回。
- (UIImage*)accessImage {
self.lastAccessDate = [NSDate date];
return self.image;
}
那這個(gè)self.image是怎么來的呢埃元?
再繼續(xù)往前找涝涤,AFCachedImage聲明了一個(gè)初始化的方法
@implementation AFCachedImage
-(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;
}
這個(gè)方法里初始化了AFCachedImage的4個(gè)屬性,分別是圖片image岛杀,標(biāo)識(shí)符identifier阔拳,totalBytes總大小(字節(jié)為單位),lastAccessDate最新訪問時(shí)間(用于清理內(nèi)存時(shí)糊肠,進(jìn)行排序)辨宠。
這個(gè)初始化,在AFAutoPurgingImageCache類中货裹,添加圖片的方法里會(huì)用到嗤形。
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
//注意,這里使用了dispatch_barrier_async弧圆,相當(dāng)于把block( ^{})中的任務(wù)排在了這個(gè)并行隊(duì)列后面赋兵,
dispatch_barrier_async(self.synchronizationQueue, ^{
1、先是通過image和identifier初始化一個(gè)AFCachedImage
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
2搔预、然后通過identifier找到cachedImages中保存的對(duì)應(yīng)對(duì)象previousCachedImage
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
3霹期、根據(jù)currentMemoryUsage是否存在重新計(jì)算currentMemoryUsage。
if (previousCachedImage != nil) {
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
self.cachedImages[identifier] = cacheImage;
self.currentMemoryUsage += cacheImage.totalBytes;
});
4拯田、另外一個(gè)dispatch_barrier_async中历造,通過currentMemoryUsage和memoryCapacity大小對(duì)比,將之前保存的緩存圖片數(shù)組根據(jù)lastAccessDate排序船庇。使用了LRU緩存策略吭产。
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;
5、將重新排序的數(shù)組溢十,遍歷數(shù)組垮刹,移除數(shù)據(jù)直到緩存容量小于等于限值
for (AFCachedImage *cachedImage in sortedImages) {
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
if (bytesPurged >= bytesToPurge) {
break ;
}
}
self.currentMemoryUsage -= bytesPurged;
}
});
}
“這個(gè)方法是核心方法,在這個(gè)方法中张弛,一共做了兩件事:
把圖片加入到緩存字典中(注意字典中可能存在identifier的情況)荒典,然后計(jì)算當(dāng)前的容量大小
處理容量超過最大容量的異常情況。分為下邊幾個(gè)步驟: 1.比較容量是否超過最大容量 2.計(jì)算將要清楚的緩存容量 3.把所有緩存的圖片放到一個(gè)數(shù)組中 4.對(duì)這個(gè)數(shù)組按照最后訪問時(shí)間進(jìn)行排序吞鸭,優(yōu)先保留最后訪問的數(shù)據(jù) 5.遍歷數(shù)組寺董,移除圖片(當(dāng)已經(jīng)移除的數(shù)據(jù)大于應(yīng)該移除的數(shù)據(jù)時(shí)停止)”