美文网首页iOS学习开发iOS学习笔记
AFNetworking图片缓存——多读单写实现方式

AFNetworking图片缓存——多读单写实现方式

作者: 王技术 | 来源:发表于2019-11-09 10:18 被阅读0次

    AFNetworking 中有 AFAutoPurgingImageCache 这个工具类, 实现了对图片的多读单写功能.
    他可脱离 AFNetworking 单独使用, 肥肠的方便.


    这是他的初始化方法 :

    - (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;
    

    memoryCapacity 是缓存最大占用内存总量
    preferredMemoryCapacity 是当达到内存总量后将删除最久未使用的图, 一直等到内存降到该值为止

    缓存工具类 AFAutoPurgingImageCache 遵循了 AFImageRequestCache 协议, AFImageRequestCache 协议是对 AFImageCache 协议的扩展
    两个协议的方法列表 :

    @protocol AFImageCache <NSObject>
    
    - (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;
    
    - (BOOL)removeImageWithIdentifier:(NSString *)identifier;
    
    - (BOOL)removeAllImages;
    
    - (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;
    @end
    
    
    
    @protocol AFImageRequestCache <AFImageCache>
    
    - (BOOL)shouldCacheImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
    
    - (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
    
    - (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
    
    - (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
    
    @end
    
    

    功能还是很明显的, 基础协议 AFImageCache 实现了图片的存取
    AFImageRequestCache 则针对网络请求的图片存取做了功能拓展
    协议中所有方法, 都会来到最终的这三个方法, 分别是对图片的

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

    为了保证图片存取的线程安全
    使用了 GCD 的栅栏实现了图片的多读单写
    GCD 的栅栏有两个函数

    dispatch_barrier_sync(queue,  ^{})
    
    
    dispatch_barrier_async(queue,  ^{})
    

    他们俩的区别就是 barrier_sync 会阻塞它之后的任务的入队
    必须等到 barrier_sync 任务执行完毕, 才会把后面的任务添加到队列中
    而 barrier_async 不需要等自身的 block 执行完成, 就可以把后面的任务添加到队列中。

    所以在 removeImageWithIdentifier: 中使用了 dispatch_barrier_sync
    在删除图片的时候, 排在删除任务后面的读写任务都得等等
    等删除操作的 block 执行完事之后其他操作再上

    在读取方法中 imageWithIdentifier:
    读取操作不会产生资源抢夺问题, 所以不需要使用栅栏函数
    使用了同步函数 dispatch_sync 同步返回图片即可

    最后就是添加图片的方法 addImage: withIdentifier:
    第一个操作是根据 identifier 用新图片替换旧图, 同时用新图片所占大小替换旧图片所占大小
    这样做是为了防止服务器做了重定向导致同一个 URL对应了不同图片

    第二个操作是是检测当前缓存中的图片所占内存大小是否超过了额定值
    如过超过了就删除最久没有使用的图片, 直到内存大小达到稳定值

    添加图片方法的两个操作都是在 dispatch_barrier_async 中执行, 不会堵塞当前线程
    但是会拦截在添加图片操作之后入队列的图片读取操作 , 肥肠稳健!

    另外还有一些栅栏函数的使用注意
    • 在使用栅栏函数时.使用自定义并发队列才有意义,如果用的是串行队列或者系统提供的全局并发队列, 这个栅栏函数的作用等同于一个同步函数的作用
    • 在使用串行队列时嵌套 dispatch_barrier_sync 也会导致死锁

    相关文章

      网友评论

        本文标题:AFNetworking图片缓存——多读单写实现方式

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