AFNetworking 3.0 源碼解讀之 AFAutoPurgingImageCache

之前在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è)功能。


image.png

還有一個(gè)協(xié)議AFImageRequestCache,它繼承自AFImageCache,又?jǐn)U展了3個(gè)方法。


image.png

在AFAutoPurgingImageCache的.m文件中申明了AFCachedImage。

@interface AFCachedImage : NSObject

@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) NSString *identifier; //圖片標(biāo)識符
@property (nonatomic, assign) UInt64 totalBytes;//圖片總大小
@property (nonatomic, strong) NSDate *lastAccessDate;//最近訪問時(shí)間
@property (nonatomic, assign) UInt64 currentMemoryUsage; // 當(dāng)前內(nèi)存容量

@end

而AFAutoPurgingImageCache的屬性中,有一個(gè)存放AFCachedImage對象的字典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è)字典派上用場了,通過identifier這個(gè)key,尋找對應(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)識符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中保存的對應(yīng)對象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大小對比,將之前保存的緩存圖片數(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.對這個(gè)數(shù)組按照最后訪問時(shí)間進(jìn)行排序,優(yōu)先保留最后訪問的數(shù)據(jù) 5.遍歷數(shù)組,移除圖片(當(dāng)已經(jīng)移除的數(shù)據(jù)大于應(yīng)該移除的數(shù)據(jù)時(shí)停止)”

參考文章:
AFNetworking 3.0 源碼解讀(七)之 AFAutoPurgingImageCache

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容