CommonTasks_Cache.md 7.0 KB

Common Tasks - Cache

Common tasks related to the ImageCache in Kingfisher.

Overview

Kingfisher employs a hybrid ImageCache for managing cached images, comprising both memory and disk storage. It offers high-level APIs for cache management. Unless otherwise specified, the ImageCache/default instance is used throughout Kingfisher.

Using another cache key

By default, the URL is converted into a string to generate the cache key. For network URLs, absoluteString is utilized. You can customize the key by creating an ImageResource object with a specified key.

let resource = ImageResource(
    downloadURL: url, 
    cacheKey: "my_cache_key"
)
imageView.kf.setImage(with: resource)

Kingfisher uses the cacheKey to locate images in the cache. Ensure you use a distinct key for each different image.

Checking whether an image in the cache

let cache = ImageCache.default
let cached = cache.isCached(forKey: cacheKey)

// To know where the cached image is:
let cacheType = cache.imageCachedType(forKey: cacheKey)
// `.memory`, `.disk` or `.none`.

If a processor is applied when retrieving an image, the processed image will be cached. In this scenario, remember to also include the processor identifier when manipulating the cache:

let processor = RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [.processor(processor)])

// Later
cache.isCached(forKey: cacheKey, processorIdentifier: processor.identifier)

Getting an image from the cache

cache.retrieveImage(forKey: "cacheKey") { result in
    switch result {
    case .success(let value):
        print(value.cacheType)

        // If the `cacheType is `.none`, `image` will be `nil`.
        print(value.image)

    case .failure(let error):
        print(error)
    }
}

Set limit for the cache

For memory storage, you can set its MemoryStorage/Config/totalCostLimit and MemoryStorage/Config/countLimit:

// Limit memory cache size to 300 MB.
cache.memoryStorage.config.totalCostLimit = 300 * 1024 * 1024

// Limit memory cache to hold 150 images at most. 
cache.memoryStorage.config.countLimit = 150

The default MemoryStorage/Config/totalCostLimit for the memory cache is set to 25% of the device's total memory, with no limit on the MemoryStorage/Config/countLimit.

For disk storage, you have the option to set a DiskStorage/Config/sizeLimit to manage the space used on the file system.

// Limit disk cache size to 1 GB.
cache.diskStorage.config.sizeLimit =  = 1000 * 1024 * 1024

Set the default expiration for cache

Both memory and disk storage in Kingfisher have default expiration settings. Images in memory storage expire 5 minutes after the last access, whereas images in disk storage expire after one week. These values can be modified as follows:

// Set memory image expires after 10 minutes.
cache.memoryStorage.config.expiration = .seconds(600)

// Set disk image never expires.
cache.diskStorage.config.expiration = .never

To override this default expiration for a specific image when caching it, include an option as follows during image setting:

// This image will never expire in memory cache.
imageView.kf.setImage(with: url, options: [.memoryCacheExpiration(.never)])

The expired memory cache is purged every 2 minutes by default. To adjust this frequency:

// Check memory clean up every 30 seconds.
cache.memoryStorage.config.cleanInterval = 30

Store images to cache manually

By default, view extension methods and KingfisherManager automatically store retrieved images in the cache. However, you can also manually store an image to the cache:

let image: UIImage = //...
cache.store(image, forKey: cacheKey)

If you possess the original data of the image, pass it along to ImageCache. This assists Kingfisher in determining the appropriate format for storing the image:

let data: Data = //...
let image: UIImage = //...
cache.store(image, original: data, forKey: cacheKey)

Remove images from cache manually

Kingfisher manages its cache automatically. But you still can manually remove a certain image from cache:

cache.removeImage(forKey: cacheKey)

Or, with more control:

cache.removeImage(
    forKey: cacheKey,
    processorIdentifier: processor.identifier,
    fromMemory: false,
    fromDisk: true)
{
    print("Removed!")
}

Clear the cache

// Remove all.
cache.clearMemoryCache()
cache.clearDiskCache { print("Done") }

// Remove only expired.
cache.cleanExpiredMemoryCache()
cache.cleanExpiredDiskCache { print("Done") }

Report the disk storage size

ImageCache.default.calculateDiskStorageSize { result in
    switch result {
    case .success(let size):
        print("Disk cache size: \(Double(size) / 1024 / 1024) MB")
    case .failure(let error):
        print(error)
    }
}

Create your own cache and use it

// The `name` parameter is used to identify the disk cache bound to the `ImageCache`.
let cache = ImageCache(name: "my-own-cache")
imageView.kf.setImage(with: url, options: [.targetCache(cache)])

Skipping cache searching, force downloading image again

imageView.kf.setImage(with: url, options: [.forceRefresh])

Only search cache for the image, do not download if not existing

This makes your app to an "offline" mode.

imageView.kf.setImage(with: url, options: [.onlyFromCache])

If the image does not exist in the cache, an KingfisherError/CacheErrorReason/imageNotExisting(key:) error will be triggered.

Waiting for cache to finish

Storing images in the disk cache is asynchronous and doesn't need to be completed before setting the image view and invoking the completion handler in view extension methods. This means that the disk cache might not be fully updated at the time the completion handler is executed, as shown below:

imageView.kf.setImage(with: url) { _ in
    ImageCache.default.retrieveImageInDiskCache(forKey: url.cacheKey) { result in
        switch result {
        case .success(let image):
            // `image` might be `nil` here.
        case .failure: break
        }
    }
}

For most scenarios, this asynchronous behavior isn't an issue. However, if your logic relies on the existence of the disk cache, use the .waitForCache option. With this option, Kingfisher will delay the execution of the handler until the disk cache operation is complete:

imageView.kf.setImage(with: url, options: [.waitForCache]) { _ in
    ImageCache.default.retrieveImageInDiskCache(forKey: url.cacheKey) { result in
        switch result {
        case .success(let image):
            // `image` exists.
        case .failure: break
        }
    }
}

This consideration applies specifically to disk image caching, which involves asynchronous I/O operations. In contrast, memory cache operations are synchronous, ensuring that the image is always available in the memory cache.