美文网首页
AFAutoPurgingImageCache 源码阅读笔记

AFAutoPurgingImageCache 源码阅读笔记

作者: 杨柳小易 | 来源:发表于2017-08-18 15:56 被阅读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>

    相关文章

      网友评论

          本文标题:AFAutoPurgingImageCache 源码阅读笔记

          本文链接:https://www.haomeiwen.com/subject/mpyarxtx.html