美文网首页
AFN AFAutoPurgingImageCache 源码分析

AFN AFAutoPurgingImageCache 源码分析

作者: 孙掌门 | 来源:发表于2020-06-28 11:25 被阅读0次

AFN AFAutoPurgingImageCache 源码分析

其实这个源码特别简单,但是却可以学习人家的思想,在我们平时开发中,使用 image 的频率是非常非常大的,但如果我们经常调用 imageNamed这个系统 api 去加载本地图片的时候,是会耗时额,imageNamed 主要做两件事,第一 I/O 操作,首次加载图片是需要去磁盘中的,所以有 I/O 操作,然后对图片进行解码,将位图数据还原为原始像素数据,这里加载图片就先不说 SD 和 YY 加载图片的技术了,这里 AFN 用的是一个内存缓存的一个思路,在平时开发中也可以应用

AFCachedImage

图片的缓存类,相当于映射系统的 UIImage , 但是比 UIImage 多了几个属性,如当前图片的大小,因为要放在缓存中,所以需要计算当前图片占用的大小,然后是图片的访问时间,因为会限定缓存图片需要占用的总大小,所以,如果超出限制需要淘汰,就是根据这个访问时间淘汰的,这就是这个类的作用

AFAutoPurgingImageCache


- (instancetype)init {
    // 默认缓存容量为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);

        // 发生内存警告之后,清除所有图片
        [[NSNotificationCenter defaultCenter]
         addObserver:self
         selector:@selector(removeAllImages)
         name:UIApplicationDidReceiveMemoryWarningNotification
         object:nil];

    }
    return self;
}

初始化缓存类,主要初始化,缓存允许的总大小,如超出总大小之后,需要留下多少缓存,比如我们允许缓存中总共存储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;
        }
    });
}

添加图片,这里使用的是栅栏函数,主要就是多读单写,这里添加图片,相当于写,单写的,每次添加图片额时候,根据ID判断缓存中是否有缓存,如果有,那么就先将当前总共占用额缓存减去已经存在的ID对应额那个图片额缓存,然后将新的ID图片添加到缓存,然后当前总缓存加上新的图片额缓存,接下来,判断当前的总缓存,是不是超过我们预定号的总额memoryCapacity缓存,如果超过,就遍历所有图片,根据访问时间进行淘汰,直到剩余preferredMemoryUsageAfterPurge这么大为止,可以比这个值小,那么就符合要求了

我们再来看取缓存大小

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

可以看到是同步多读的,,利用栅栏函数实现多读单写


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


删除也是个互斥单操作

- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
    __block UIImage *image = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        AFCachedImage *cachedImage = self.cachedImages[identifier];
        image = [cachedImage accessImage];
    });
    return image;
}


从缓存中读取图片,是个同步多读的操作,这就是栅栏函数的魅力。


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

这里主要是根据 URL 来缓存图片,根据请求的url和一个ID,来唯一确定一个ID,来缓存图片

相关文章

网友评论

      本文标题:AFN AFAutoPurgingImageCache 源码分析

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