zoukankan      html  css  js  c++  java
  • AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

    这篇我们就要介绍AFAutoPurgingImageCache这个类了。这个类给了我们临时管理图片内存的能力。

    前言

    假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来管理应用内的所有的下载事件。至于下载器能够提供的功能,在此先不做说明。但在 AFAutoPurgingImageCache 中我们能够借鉴一些东西。

    AFImageCache

    通过这个协议,我们能够做下边四件事:

    AFImageRequestCache

    这个协议继承自AFImageCache,然后又扩展了下边三个方法:

    AFAutoPurgingImageCache

    它集成了AFImageRequestCache协议,因此上图中的方法都会实现。我们先看看它暴露出来的有哪些东西:

    1. UInt64 memoryCapacity 总共的内存容量
    2. UInt64 preferredMemoryUsageAfterPurge 当清空时优先保存的容量
    3. UInt64 memoryUsage 当前已使用的容量
    4. init 初始化方法
    5. initWithMemoryCapacity: preferredMemoryCapacity: 初始化方法

    AFCachedImage

    AFCachedImage用来抽象被缓存的图片,看到这个对象,我联想到一个下载器也需要一个这样的被下载的对象的抽象描述类。我们需要一些属性来描述这个被缓存的图片。

    1. UIImage *image 图片
    2. NSString *identifier 标识
    3. UInt64 totalBytes 总大小,已字节为单位
    4. NSDate *lastAccessDate 最后的访问时间,用于清理内存时,进行排序
    5. UInt64 currentMemoryUsage 当前的容量使用情况

    --

    -(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);
            
            // 每个像素占用4个字节
            CGFloat bytesPerPixel = 4.0;
            //这个是指图片中有多少个像素,这个名称bytesPerSize改为pixelsPerSize是不是更加贴切呢?
            CGFloat bytesPerSize = imageSize.width * imageSize.height;
            self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
            self.lastAccessDate = [NSDate date];
        }
        return self;
    }
    

    --

    - (UIImage*)accessImage {
        self.lastAccessDate = [NSDate date];
        return self.image;
    }
    
    - (NSString *)description {
        NSString *descriptionString = [NSString stringWithFormat:@"Idenfitier: %@  lastAccessDate: %@ ", self.identifier, self.lastAccessDate];
        return descriptionString;
    
    }
    

    AFAutoPurgingImageCache实现部分

    既然是图片的临时缓存类,那么我们应该把图片缓存到什么地方呢?答案就是一个字典中。值得注意的是,我们缓存使用的是一个同步的队列

    1. NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages 存放图片
    2. UInt64 currentMemoryUsage 当前使用的容量
    3. dispatch_queue_t synchronizationQueue 队列

    --

    - (instancetype)init {
        return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
    }
    

    通过这个方法,我们就能够看出默认的缓存容量的大小为100M,清除后保存容量为60M

    - (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;
    }
    

    --

    - (UInt64)memoryUsage {
        __block UInt64 result = 0;
        dispatch_sync(self.synchronizationQueue, ^{
            result = self.currentMemoryUsage;
        });
        return result;
    }
    

    --

    - (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;
            }
        });
    }
    

    这个方法是核心方法,我们重点介绍下,在这个方法中,一共做了两件事:

    1. 把图片加入到缓存字典中(注意字典中可能存在identifier的情况),然后计算当前的容量大小
    2. 处理容量超过最大容量的异常情况。分为下边几个步骤: 1.比较容量是否超过最大容量 2.计算将要清楚的缓存容量 3.把所有缓存的图片放到一个数组中 4.对这个数组按照最后访问时间进行排序,优先保留最后访问的数据 5.遍历数组,移除图片(当已经移除的数据大于应该移除的数据时停止)

    ps: 这里不得不讲一下 dispatch_barrier_async 这个方法。barrier 这个单词的意思是障碍,拦截的意思,也即是说dispatch_barrier_async一定是有拦截事件的作用。

    看下边这段代码:

     dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(concurrentQueue, ^(){
            NSLog(@"dispatch-1");
        });
        dispatch_async(concurrentQueue, ^(){
            NSLog(@"dispatch-2");
        });
        dispatch_barrier_async(concurrentQueue, ^(){
            NSLog(@"dispatch-barrier");
        });
        dispatch_async(concurrentQueue, ^(){
            NSLog(@"dispatch-3");
        });
        dispatch_async(concurrentQueue, ^(){
            NSLog(@"dispatch-4");
        });
    

    打印结果:

    2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1
    2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2
    2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier
    2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3
    2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
    

    这个说明了dispatch_barrier_async能够拦截它前边的异步事件,等待两个异步方法都完成之后,调用dispatch_barrier_async

    我们稍微改动一下:

    dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-1");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-2");
    });
    dispatch_barrier_sync(concurrentQueue, ^(){
        NSLog(@"dispatch-barrier");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-3");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-4");
    });
    

    打印结果:

    2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1
    2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2
    2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier
    2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3
    2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
    

    --

    - (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
        NSString *key = request.URL.absoluteString;
        if (additionalIdentifier != nil) {
            key = [key stringByAppendingString:additionalIdentifier];
        }
        return key;
    }
    

    通过这个方法可以看出,使用NSURLRequest进行缓存的时候,也只是使用了request.URL.absoluteString + additionalIdentifier 来作为缓存字典的key。在这里其他协议的实现方法就不做介绍了。

    总结

    通过这个文件,提供给了我们一个关于下载器 下载后的文件的一个封装的思路。按照正常来说,下载后的文件的标识应该就是URL。

    推荐阅读

    AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager

    AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy

    AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

    AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

    AFNetworking 3.0 源码解读(五)之 AFURLSessionManager

    AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

  • 相关阅读:
    angular面试记忆的内容
    doctype
    161214、oracle查询表信息
    161213、Maven资源替换和Freemarker模板
    161212、并发编程中的关于队列
    161209、简要分析ZooKeeper基本原理及安装部署
    161208、Java enum 枚举还可以这么用
    161207、高并发:java.util.concurrent.Semaphore实现字符串池及其常用方法介绍
    161206、 Ionic、Angularjs、Cordova搭建Android开发环境
    161205、win10安装mysql5.7.16数据库
  • 原文地址:https://www.cnblogs.com/machao/p/5796234.html
Copyright © 2011-2022 走看看