AFAutoPurgingImageCache 源码阅读笔记

2017-08-18  本文已影响15人  杨柳小易

<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>

上一篇下一篇

猜你喜欢

热点阅读