iOS-Kingfisher个人浅析(缓存篇)

写在前面,当时学习Kingfisher的源码时因为工作的原因中途放下了,只写到图像下载,即ImageDownloader模块,图像下载的代码因为各种函数作为形参,闭包声明实现等等看的我是云里雾里的,所以把那部分的代码写成了一篇,本以为ImageCache相关模块也是同样的晦涩代码,所以打算另外边看边写一篇,结果发现ImageCache的代码相较于ImageDownloader清晰简单很多,写到一半的时候才发现很多东西都不用写出来,写出来反而感觉自己像个智障一样这种小事都要写出来,因此缓存篇就只能草草了事ORZ。

衔接上文,我们回到位于KingfisherManager中的cacheImage函数。

 func cacheImage(_ result: Result)
        {
            switch result {
            case .success(let value):
                // Add image to cache.
                let targetCache = options.targetCache ?? self.cache
                targetCache.store(
                    value.image,
                    original: value.originalData,
                    forKey: source.cacheKey,
                    options: options,
                    toDisk: !options.cacheMemoryOnly)
                {
                    _ in
                    if options.waitForCache {
                        let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
                        completionHandler?(.success(result))
                    }
                }

                // Add original image to cache if necessary.
                let needToCacheOriginalImage = options.cacheOriginalImage &&
                    options.processor != DefaultImageProcessor.default
                if needToCacheOriginalImage {
                    let originalCache = options.originalCache ?? targetCache
                    originalCache.storeToDisk(
                        value.originalData,
                        forKey: source.cacheKey,
                        processorIdentifier: DefaultImageProcessor.default.identifier,
                        expiration: options.diskCacheExpiration)
                }
                
                print("needToCacheOriginalImage: \(needToCacheOriginalImage)")

                if !options.waitForCache {
                    let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
                    completionHandler?(.success(result))
                }
                
            case .failure(let error):
                completionHandler?(.failure(error))
            }
        }

上篇我们知道,cacheImage函数是作为形参传入ImageDownloader中,在网络请求数据的结尾被回调。
首先逐行分析cacheImage函数内部代码:

let targetCache = options.targetCache ?? self.cache

同样的,我们可以通过KingfisherOptionsInfo配置我们自己的缓存管理对象,而当我们不传入时KingfisherManager在单例初始化时初始化了一个命名为default的缓存管理对象ImageCache

targetCache.store(
                    value.image,
                    original: value.originalData,
                    forKey: source.cacheKey,
                    options: options,
                    toDisk: !options.cacheMemoryOnly)
                {
                    _ in
                    if options.waitForCache {
                        let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
                        completionHandler?(.success(result))
                    }
                }

ImageCache实例对象调用store函数缓存请求成功的图像数据,接收数个形参图像、图像数据、URL地址的缓存标识符以及配置对象以及是否缓存进磁盘(默认为true),并提供一个缓存结束的回调函数,当配置对象配置了要求在缓存结束才进行回调时,相应的逻辑被激活。
而不等待缓存结束则会在store进行的下一步即刻返回:

if !options.waitForCache {
                    let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
                    completionHandler?(.success(result))
                }
// Add original image to cache if necessary.
                let needToCacheOriginalImage = options.cacheOriginalImage &&
                    options.processor != DefaultImageProcessor.default
                if needToCacheOriginalImage {
                    let originalCache = options.originalCache ?? targetCache
                    originalCache.storeToDisk(
                        value.originalData,
                        forKey: source.cacheKey,
                        processorIdentifier: DefaultImageProcessor.default.identifier,
                        expiration: options.diskCacheExpiration)
                }

若需要缓存原始图片则进行大致相同的逻辑,将数据写入磁盘。

接下来看store函数内部代码:

  open func store(_ image: Image,
                    original: Data? = nil,
                    forKey key: String,
                    options: KingfisherParsedOptionsInfo,
                    toDisk: Bool = true,
                    completionHandler: ((CacheStoreResult) -> Void)? = nil)
    {
        print("options.processor.identifier: \(options.processor.identifier)")
        let identifier = options.processor.identifier
        let callbackQueue = options.callbackQueue
        
        let computedKey = key.computedKey(with: identifier)
        // Memory storage should not throw.
        memoryStorage.storeNoThrow(value: image, forKey: computedKey, expiration: options.memoryCacheExpiration)
        
        guard toDisk else {
            if let completionHandler = completionHandler {
                let result = CacheStoreResult(memoryCacheResult: .success(()), diskCacheResult: .success(()))
                callbackQueue.execute { completionHandler(result) }
            }
            return
        }
        
        ioQueue.async {
            let serializer = options.cacheSerializer
            if let data = serializer.data(with: image, original: original) {
                self.syncStoreToDisk(
                    data,
                    forKey: key,
                    processorIdentifier: identifier,
                    callbackQueue: callbackQueue,
                    expiration: options.diskCacheExpiration,
                    completionHandler: completionHandler)
            } else {
                guard let completionHandler = completionHandler else { return }
                
                let diskError = KingfisherError.cacheError(
                    reason: .cannotSerializeImage(image: image, original: original, serializer: serializer))
                let result = CacheStoreResult(
                    memoryCacheResult: .success(()),
                    diskCacheResult: .failure(diskError))
                callbackQueue.execute { completionHandler(result) }
            }
        }
    }

逻辑大致分为两部分,分别是必然执行的缓存入内存空间和可选择执行的缓存入磁盘,我们先看内存缓存的相关代码

        memoryStorage.storeNoThrow(value: image, forKey: computedKey, expiration: options.memoryCacheExpiration)

memoryStorage的功能是作为所有的内存缓存数据的容器,memoryStorageMemoryStorage.Backend类型的实例对象,其在ImageCache初始化时完成初始化,而我们知道,ImageCache的实例对象被KingfisherManager的单例所持有,所以memoryStorage会一直保存记录在其中的缓存数据,直到KingfisherManager因为各种因素而被销毁。
进入storeNoThrow函数我们可以发现,函数内部利用传入的形参初始化了一个StorageObject对象,该对象作为内存缓存基本单元,记录了关于数据、标识符以及过期时间的相关信息,然后,这个基本单元被插入到MemoryStorage.Backend对象内部维护的一个NSCache对象中,由此,图像数据在内存中的缓存就完成了。

既然缓存完成,我们自然要监听内存缓存该什么时候过期销毁。
MemoryStorage.Backend对象在初始化函数内部初始化了一个定时器,该定时器默认120秒循环一次,通过MemoryStorage.Backend对象内部维护的标识符数组获取每一个缓存对象,获取初始化时记录的过期时间进行过期销毁操作。

接着是磁盘缓存:

let serializer = options.cacheSerializer
            if let data = serializer.data(with: image, original: original) {
                self.syncStoreToDisk(
                    data,
                    forKey: key,
                    processorIdentifier: identifier,
                    callbackQueue: callbackQueue,
                    expiration: options.diskCacheExpiration,
                    completionHandler: completionHandler)
            }

在开始缓存之前,所有的图像数据都会被转化为Data类型,而根据Data的头部byte的数据差异使用不同的格式转化。

public func data(format: ImageFormat) -> Data? {
        let data: Data?
        switch format {
        case .PNG: data = pngRepresentation()
        case .JPEG: data = jpegRepresentation(compressionQuality: 1.0)
        case .GIF: data = gifRepresentation()
        case .unknown: data = normalized.kf.pngRepresentation()
        }

        return data
    }

当数据正确转化完成时,开始进行子线程的同步任务缓存入磁盘。
进入syncStoreToDisk函数,我们可以看到,这一层的逻辑主要是调用diskStorage对象进行磁盘存储,以及根据成功或者失败,在主线程中回调信息。
diskStorage对象和memoryStorage相同,都是在ImageCache的初始化函数中进行初始化并持有,ImageCache的初始化函数根据外部入参初始化了DiskStorage.Config的配置对象,并利用配置对象对diskSrorage进行初始化,其中涉及到磁盘缓存的具体沙盒路径、缓存文件夹的创建以及元更改队列的初始化等等。

回到diskStorage的磁盘写入调用,我们查看store函数的内部代码可以发现其逻辑大致与memoryStorage相类似,通过config配置对象初始化的FileManager对象向沙盒内写入数据。

config.fileManager.createFile(atPath: fileURL.path, contents: data, attributes: attributes)

内存写入与磁盘写入的逻辑大致就是这样,虽然其中牵扯到一些许多关于初始化数据配置的代码,这部分因为过于分散难以一一描述。

回到KingfisherManager,在retrieveImageFromCache函数中我们从内存缓存和磁盘缓存中获取图像数据。

 func retrieveImageFromCache(
        source: Source,
        options: KingfisherParsedOptionsInfo,
        completionHandler: ((Result) -> Void)?) -> Bool
    {
        // 1. Check whether the image was already in target cache. If so, just get it.
        let targetCache = options.targetCache ?? cache
        let key = source.cacheKey
        let targetImageCached = targetCache.imageCachedType(
            forKey: key, processorIdentifier: options.processor.identifier)
        
        let validCache = targetImageCached.cached &&
            (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory)
        if validCache {
            targetCache.retrieveImage(forKey: key, options: options) { result in
                guard let completionHandler = completionHandler else { return }
                options.callbackQueue.execute {
                    result.match(
                        onSuccess: { cacheResult in
                            let value: Result
                            if let image = cacheResult.image {
                                value = result.map {
                                    RetrieveImageResult(image: image, cacheType: $0.cacheType, source: source)
                                }
                            } else {
                                value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
                            }
                            completionHandler(value)
                        },
                        onFailure: { _ in
                            completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
                        }
                    )
                }
            }
            return true
        }
        let targetImageCached = targetCache.imageCachedType(
            forKey: key, processorIdentifier: options.processor.identifier)

我们首先需要判断唯一标示符是否存在于缓存数据中,先从内存缓存中搜寻然后再从磁盘缓存中搜寻。
在上面已经提过,内存缓存中的数据都被保存在MemoryStorage实例对象的名为storageNSCache实例对象中,通过objectForKey函数获取判断Key是否有对应的Object
而磁盘缓存则是通过唯一标识符拼装成沙盒路径,通过FileManager获取本地数据来判断是否有值。

当我们尝试获取磁盘缓存时,磁盘缓存管理对象会尝试生成一个FileMeta对象,该对象会根据我们给定的拼装路径地址,根据配置字典请求该路径的目标文件的相关信息,这些信息包括文件的创建日期、修改日期、文件大小等等,通过创建日期来判断该文件是否过期,是否返回数据给外部。而当我们成功获取到磁盘数据时,FileMeta对象也会延长数据文件在磁盘中的到期时间。

到这里有个我有个疑点,我们通过targetImageCached枚举对象了解到数据是否存在于内存或者磁盘中,但是枚举对象的数据只用来生成validCache布尔值,没有进入后面的逻辑判断,在进入retrieveImage函数时,没有使用targetImageCached直接选择是获取内存数据还是磁盘数据,反而依然是先进入内存缓存获取数据,获取不到再进入磁盘缓存代码,不得不说这个代码有些让人难以理解。

retrieveImage函数内部与我们之前targetImageCached的初始化代码有很类似的结构,毕竟我们在初始化targetImageCached时已经询问过内存管理对象或是磁盘管理对象是否存在唯一标示符对应的数据了,它实际上就是尝试从内存管理对象内部取出相应的数据,判断是否取出成功,磁盘也是相同的道理,只是我们存储在内存管理对象中的是Image类型的数据,而磁盘中存储的是Data类型的数据,在从磁盘中获取后还要经过与插入数据到本地相反的转化过程,最终转化为Image数据传递给外部。

你可能感兴趣的:(iOS-Kingfisher个人浅析(缓存篇))