AFN AFAutoPurgingImageCache 源碼分析
其實(shí)這個(gè)源碼特別簡(jiǎn)單许溅,但是卻可以學(xué)習(xí)人家的思想汹忠,在我們平時(shí)開發(fā)中,使用 image 的頻率是非常非常大的,但如果我們經(jīng)常調(diào)用 imageNamed這個(gè)系統(tǒng) api 去加載本地圖片的時(shí)候乾蓬,是會(huì)耗時(shí)額河劝,imageNamed 主要做兩件事壁榕,第一 I/O 操作,首次加載圖片是需要去磁盤中的赎瞎,所以有 I/O 操作牌里,然后對(duì)圖片進(jìn)行解碼,將位圖數(shù)據(jù)還原為原始像素?cái)?shù)據(jù),這里加載圖片就先不說 SD 和 YY 加載圖片的技術(shù)了牡辽,這里 AFN 用的是一個(gè)內(nèi)存緩存的一個(gè)思路喳篇,在平時(shí)開發(fā)中也可以應(yīng)用
AFCachedImage
圖片的緩存類,相當(dāng)于映射系統(tǒng)的 UIImage 态辛, 但是比 UIImage 多了幾個(gè)屬性麸澜,如當(dāng)前圖片的大小,因?yàn)橐旁诰彺嬷凶嗪冢孕枰?jì)算當(dāng)前圖片占用的大小炊邦,然后是圖片的訪問時(shí)間,因?yàn)闀?huì)限定緩存圖片需要占用的總大小熟史,所以馁害,如果超出限制需要淘汰,就是根據(jù)這個(gè)訪問時(shí)間淘汰的蹂匹,這就是這個(gè)類的作用
AFAutoPurgingImageCache
- (instancetype)init {
// 默認(rèn)緩存容量為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);
// 發(fā)生內(nèi)存警告之后,清除所有圖片
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(removeAllImages)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
初始化緩存類限寞,主要初始化忍啸,緩存允許的總大小,如超出總大小之后履植,需要留下多少緩存计雌,比如我們?cè)试S緩存中總共存儲(chǔ)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;
}
});
}
添加圖片鼠渺,這里使用的是柵欄函數(shù)鸭巴,主要就是多讀單寫,這里添加圖片拦盹,相當(dāng)于寫鹃祖,單寫的,每次添加圖片額時(shí)候普舆,根據(jù)ID判斷緩存中是否有緩存恬口,如果有,那么就先將當(dāng)前總共占用額緩存減去已經(jīng)存在的ID對(duì)應(yīng)額那個(gè)圖片額緩存沼侣,然后將新的ID圖片添加到緩存祖能,然后當(dāng)前總緩存加上新的圖片額緩存,接下來蛾洛,判斷當(dāng)前的總緩存养铸,是不是超過我們預(yù)定號(hào)的總額memoryCapacity緩存雁芙,如果超過,就遍歷所有圖片钞螟,根據(jù)訪問時(shí)間進(jìn)行淘汰兔甘,直到剩余preferredMemoryUsageAfterPurge這么大為止,可以比這個(gè)值小鳞滨,那么就符合要求了
我們?cè)賮砜慈【彺娲笮?/p>
// 多讀單寫
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
可以看到是同步多讀的洞焙,,利用柵欄函數(shù)實(shí)現(xiàn)多讀單寫
- (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;
}
刪除也是個(gè)互斥單操作
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
image = [cachedImage accessImage];
});
return image;
}
從緩存中讀取圖片拯啦,是個(gè)同步多讀的操作澡匪,這就是柵欄函數(shù)的魅力。
- (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;
}
這里主要是根據(jù) URL 來緩存圖片提岔,根據(jù)請(qǐng)求的url和一個(gè)ID仙蛉,來唯一確定一個(gè)ID笋敞,來緩存圖片