细读SDWebImage 源码- SDImageCache 篇

2020-07-13  本文已影响0人  三三哥

前言:本文章非快餐搬运,需要花点耐心阅读,因为是结合自己理解从Manager 开始一个方法一个方法去阅读解析,这个过程是痛苦的、但是读完之后是喜悦的,也希望指出个人理解不准确的地方,大家共同进步。

文章设定:较少篇幅的代码片段直接复制过来解析说明,篇幅较长的就上截图了,这样也方便各位观看。

本篇文章采用属性定位代码片段方法来探究SDImageCacheConfig 的一些配置属性具体作用,而不仅仅通过头文件的注释去理解


进入SDImageCache单列里面看:

缓存单列可以看到 默认以 default为名称建立初始化路径

    进入 makeDiskCachePath 看看:

可以看出 sd 是以 缓存路径,用户域名为空间 来开辟缓存路径(这个可以自己点进去看枚举解释,这里不过多说明了),只是这个路径下系统会在内存告急的情况下自动清除磁盘缓存。所以SD 做了一个弱引用表优化读取效率(后面会解释)。

我们回到 initWithNamespace这方法,继续向下阅读 initWithNamespace:

这方法里面做的事情较多主要分为 初始化缓存配置、内存缓存、硬盘缓存,已经注册 2个通知 deleteOldFiles,backgroundDeleteOkdFiles

进入 SDImageCacheConfig里面看看。

看完SDImageCacheConfig 在进入  SDMemoryCache里面看看:

SDMemoryCache 是SDImageCache 的隐藏的类,外面看不到。,先看看其初始化的方法里面干了啥

弱引用表就是针对cache数据在缓存一次,但是它仅仅是引用而不持有,所以不影响引用计数,当图片缓存被系统清除的时候,但是图片其实还是被某个UIImageView或者其他的实例持有的可以再次恢复,避免此时从磁盘里面读图片(I/O口耗性能)。

SDMemoryCache 就是继承 NSCache,里面重写了NSCache 的一些存/取对象的方法,主要是为了增加weakCache一层,随便找个例子看看:

// `setObject:forKey:` just call this with 0 cost. Override this is enough

- (void)setObject:(id)objforKey:(id)keycost:(NSUInteger)g {

    [super setObject:obj forKey:key cost:g];

    if (!self.config.shouldUseWeakMemoryCache) {

        return;

    }

    if(key && obj) {

        // Store weak cache

        LOCK(self.weakCacheLock);

        [self.weakCache setObject:obj forKey:key];

        UNLOCK(self.weakCacheLock);

    }

}

可以看出  [super setObject:obj forKey:key cost:g ]; 调用父类缓存对象,下面接着判断是否有弱内存缓存标志,有的话就直接在二次缓存一下。 其他比如取数据接口基本类似。

特别注意一点就是

- (void)didReceiveMemoryWarning:(NSNotification*)notification {

    // Only remove cache, but keep weak cache

    [super removeAllObjects];

}

内存告急的情况下 只删除 NSCache 数据,保持 weakCache 里面的数据

SDMemoryCache 相对简单一点,现在我们回到SDImageCache 初始化方法里面继续往下看:

     // Init the disk cache

        if(directory !=nil) {

            _diskCachePath= [directorystringByAppendingPathComponent:fullNamespace];

        }else{

            NSString*path = [self makeDiskCachePath:ns];

            _diskCachePath= path;

        }

        dispatch_sync(_ioQueue, ^{

            self.fileManager= [NSFileManager new];

        });

有本地路径就进行磁盘缓存,没啥好讲的,继续往下看:

#if SD_UIKIT

        // Subscribe to app events

        [[NSNotificationCenter defaultCenter] addObserver:self

                                                 selector:@selector(deleteOldFiles)

                                                     name:UIApplicationWillTerminateNotification

                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self

                                                 selector:@selector(backgroundDeleteOldFiles)

                                                     name:UIApplicationDidEnterBackgroundNotification

                                                   object:nil];

#endif

    }

两个通知,分别是 app 生命周期结束 和 进入后台,两个通知的方法最终走的都是同一个

可以看752 行和639 行所调用的方法,唯一区别是 752 利用block 做了一些归还系统进入后台任务的操作而已

我们进入deleteOldFilesWithCompletionBlock 里面看看,这个方法在SDImageCacheConfig 介绍属性的时候也介绍过,这里在详细的过一遍:开始上图

代码片段1主要是做了文件磁盘缓存以修改还是以最近操作的时间算:

 NSURLContentAccessDateKey 最近操作时间

NSURLContentModificationDateKey 最后修改时间

resourceKeys 是所需要遍历的文件的类型(是路径,按照什么时间类型的文件,以字节为单位分配文件),使用NSFileManager 的枚举方法获取 NSDirectoryEnumerator *fileEnumerator

这个遍历干了两件事:1.移除过期的文件(expiration date) 2. 存储新的文件并记录大小

resourceValues 是以NSURLResourceKey 为key,id 为value字典(id类型根据 key的含义存储对应的数据)

688 行 就是根据时间对把文件添加到删除的数组里面,循环结束删除。 cacheContentDateKey 是时间属性的key所以取出来的value 是NSDate

695 行 NSURLTotalFileAllocatedSizeKey 是大小属性的key,所以取出来文件大小。

700 行就是遍历删除文件操作

706 行开始 就是通过设定的最大缓存大小和当前文件实际大小对比

708 先定义要清掉的大小 为设定最大缓存大小的1/2

711 利用系统的排序方法按照缓存时间进行排序

717  通过遍历目前缓存文件,cacheFiles 就是fileURL 和resourceValues的映射,看一参考上一张图679 行,

一直到遍历结束 可知,清到当前实际缓存的文件大小小雨于 desiredCacheSize 为止

729 直接以block 返回到调用处

至此 SDImageCache的初始化方法全都剖析完成。肯定有一些不正确的地方,希望广大网友留言指正。


再挑几个比较重要的接口方法剖析一下:比如缓存数据,读取数据这些

直接去.m 看实现了:

287 异常处理

294 针对的是内存缓存,SDImageCacheConfig里面已经说过了,感兴趣的进去看看。

299  toDisk,我们提取核心思想,缓存磁盘 SD单独开了异步线程去做,并且检查了图片是否含有通道,我们去SDCGImageRefContainsAlpha看看:

意图很明显,kCGImageAlphaNone、kCGImageAlphaNoneSkipFirst、kCGImageAlphaNoneSkipLast 都是不包含通道的。

回到函数调用处继续往下看

307-309 区分出来是什么格式图片

311 我们进入编码方法实现里看看:

self.coders 看下头文件解释:

/**

 All coders in coders manager. The coders array is a priority queue, which means the later added coder will have the highest priority

 */

@property (nonatomic, copy, readwrite, nullable) NSArray<id<SDWebImageCoder>> *coders;

是一个遵循SDWebimageCoder 协议的对象队列,后进先出,最后加入进来的具有最高执行的优先级。

142 行我们进入具体编码实现看看:

目前SD里面有SDWebImageGIFCoder和 SDWebImageImageIOCoder 两个类遵循实现了图片编码操作,一个个的来看看

先进入SDWebImageImageIOCoder:

380 - 393 异常判断和格式图片格式判断,没啥好说的,

395 利用传进来的参数 format 获取 系统的CFStringRef,sd_UTTypeFromSDImageFormat 方法里面就是枚举获取。也不用多介绍了

398 创建目标图片CGImageDestinationRef 

/* Create an image destination writing to `data'. The parameter `type'

 * specifies the type identifier of the resulting image file.  Constants for

 * `type' are found in the LaunchServices framework header UTCoreTypes.h.  The

 * parameter `count' specifies number of images (not including thumbnails)

 * that the image file will contain. The `options' dictionary is reserved

 * for future use; currently, you should pass NULL for this parameter. */

IMAGEIO_EXTERN CGImageDestinationRef _iio_Nullable CGImageDestinationCreateWithData(CFMutableDataRef _iio_Nonnull data, CFStringRef _iio_Nonnull type, size_t count, CFDictionaryRef _iio_Nullableoptions)  IMAGEIO_AVAILABLE_STARTING(10.4, 4.0);

通过注释可以知道,创建一个指定的图片“容器”对象 用来存放CGImage

406 -407  就是把图片的UIImageOrientation 转换成kCGImagePropertyOrientation的方向

411  把image加入到CGImageDestinationRef 中

414 - 421 没啥好介绍的,释放C的指针以及返回上层。


下面我们进入SDWebImageGIFCoder 这个编码类来看看,总体和 SDWebImageImageIOCoder类似,只不过加了 针对frame 的处理,还是挑重点看:

148 提取gif图片的帧,转换成SDWebImageFrame 帧对象,这个对象有两个属性,

/**

 The image of current frame. You should not set an animated image.

 */

@property (nonatomic, strong, readonly, nonnull) UIImage *image;

/**

 The duration of current frame to be displayed. The number is seconds but not milliseconds. You should not set this to zero.

 */

@property (nonatomic, readonly, assign) NSTimeInterval duration;

进入到framesFromAnimatedImage里面看看具体怎样把GIF 转换成 SDWebImageFrame 

87 - 103 初始化 frames 并且求动画平均值。

106 - 126 :抽取图片每一个frame,转换成 SDWebImageFrame, index这个变量好像是多余的,就是index++ 其他地方没用到。不知道SD作者想干啥

条件定义的else 应该是针对的macOS ,进不了对应的类去查看(不知掉为啥),看代码逻辑和前面的一样,遍历处理每一帧然后转换成SDWebImageFrame

我们继续回到encodedDataWithImage里面往下看:

提取核心内容160 - 170 :

image.sd_imageLoopCount 在解码的时候就已经赋值了(暂不追究怎么来的),用的是关联属性去做的:

162 - 163 : 为imageDestination 这个“容器”设置一下 图片帧数量的映射,

165 - 170:  循环添加gif图片到imageDestination里面(和SDWebImageImageIOCoder里面类似)

解码的具体实现基本看完了,我门继续回到入口处(别晕了):

317 :解码完成之存一下,后面就是block完成返回了。


开放了直接存入磁盘的接口,里面调用的就是 storeImageDataToDisk,没啥可说的了

查找缓存:二重查找

430 从内存SDCacheImage里面查,但是 没有从弱hash表里面查(个人有点疑问题)

436 从磁盘查

SDImageCache 比较重要的接口细究到此结束,欢迎大家留言相互交流。

上一篇 下一篇

猜你喜欢

热点阅读