Skip to content

Cheat Sheet

onevcat edited this page Dec 8, 2018 · 34 revisions

Feel free to copy & paste code below.

This documentation will describe some most common usage of Kingfisher. The code snippet is based on iOS. However, the similar code should also work for other platforms like macOS or tvOS, by replacing the corresponding class (such as UIImage to NSImage, etc).

This guide covers the most useful part of Kingfisher components. If you want to know the detail of them, please also check the full API Reference.

Most common tasks

The view extension based APIs (for UIImageView, NSImageView, UIButton and NSButton) should be your first choice whenever possible. It keeps your code simple and elegant.

Setting image with a URL

let url = URL(string: "https://example.com/image.jpg")
imageView.kf.setImage(with: url)

This simple code:

  1. Checks whether an image is cached under the key url.absoluteString.
  2. If an image was found in the cache (either in memory or disk), sets it to imageView.image.
  3. If not, creates a request and download it from url.
  4. Converts the downloaded data to a UIImage object.
  5. Caches the image to memory cache, and store the data to the disk cache.
  6. Sets the imageView.image to display it.

Later, when you call setImage with the same url again, only the first two steps will be performed, unless the cache is purged.

Showing a placeholder

let image = UIImage(named: "default_profile_icon")
imageView.kf.setImage(with: url, placeholder: image)

The image will show in the imageView while downloading from url.

You could also use a customized UIView or NSView as placeholder, by conforming it to Placeholder:

class MyView: UIView { /* Your implementation of view */ }

extension MyView: Placeholder { /* Just leave it empty */}

imageView.kf.setImage(with: url, placeholder: MyView())

The MyView instance will be added to / removed from the imageView as needed.

Showing a loading indicator while downloading

imageView.kf.indicatorType = .activity
imageView.kf.setImage(with: url)

Show a UIActivityIndicatorView in center of image view while downloading.

Fading in downloaded image

imageView.kf.setImage(with: url, options: [.transition(.fade(0.2))])

Completion handler

imageView.kf.setImage(with: url) { result in
    // `result` is either a `.success(RetrieveImageResult)` or a `.failure(KingfisherError)`
    switch result {
    case .success(let value):
        // The image was set to image view:
        print(value.image)

        // From where the image was retrieved:
        // - .none - Just downloaded.
        // - .memory - Got from memory cache.
        // - .disk - Got from disk cache.
        print(value.cacheType)

        // The source object which contains information like `url`.
        print(value.source)

    case .failure(let error):
        print(error) // The error happens
    }
}

Round conner image

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

There are also a bunch of built-in processors in Kingfisher. See the Processor section below.

Getting an image without UI

Sometimes, you just want to get the image with Kingfisher instead of setting it to an image view. Use KingfisherManager for it:

KingfisherManager.shared.retrieveImage(with: url) { result in 
    // Do something with `result`
}

Cache

Kingfisher is using a hybrid ImageCache to manage the cached images, It consists of a memory storage and a disk storage, and provides high-level APIs to manipulate the cache system. If not specified, the ImageCache.default instance will be used across in Kingfisher.

Using another cache key

By default, The absoluteString of url will be used as the cache key. You can change it by creating an ImageResource with your own key.

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

Kingfisher will use the cacheKey to search images in cache later. Use a different key for a different image.

Check 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 you used a processor when you retrieve the image, the processed image will be stored in cache. In this case, also pass the processor identifier:

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

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

Get an image from 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 cache

For memory storage, you can set its totalCostLimit and 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

By default, the totalCostLimit of memory cache is 25% of your total memory in the device, and there is no limit on image count.

For disk storage, you can set sizeLimit for space on the file system.

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

Set default expiration for cache

Both memory storage and disk storage have default expiration setting. Images in memory storage will expire after 5 minutes from last accessed, while it is a week for images in disk storage. You can change this value by:

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

// Disk image never expires.
cache.diskStorage.config.expiration = .never

If you want to override this expiration for a certain image when caching it, pass in with an option:

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

The expired memory cache will be purged with a duration of 2 minutes. If you want it happens more frequently:

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

Store images to cache manually

By default, view extension methods and KingfisherManager will store the retrieved image to cache automatically. But you can also store an image to cache yourself:

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

If you have the original data of that image, also pass it to ImageCache, it helps Kingfisher to determine in which format the image should be stored:

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.default.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)])

Downloader

ImageDownloader wraps a URLSession for downloading an image from the Internet. Similar to ImageCache, there is an ImageDownloader.default for downloading tasks.

Downloading an image manually

Usually, you may use Kingfisher's view extension methods or KingfisherManager to retrieve an image. They will try to search in the cache first to prevent unnecessary download task. In some cases, if you only want to download a target image without caching it:

let downloader = ImageDownloader.default
downloader.downloadImage(with: url) { result in
    switch result {
    case .success(let value):
        print(value.image)
    case .failure(let error):
        print(error)
    }
}

Modify a request before sending

When you have permission control for your image resource, you can modify the request by using a .requestModifier:

let modifier = AnyModifier { request in
    var r = request
    r.setValue("abc", forHTTPHeaderField: "Access-Token")
    return r
}
downloader.downloadImage(with: url, options: [.requestModifier(modifier)]) { 
    result in
    // ...
}

// This option also works for view extension methods.
imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])

Cancelling a download task

If the downloading started, a DownloadTask will be returned. You can use it to cancel an on-going download task:

let task = downloader.downloadImage(with: url) { result in
    // ...
    case .failure(let error):
        print(error.isTaskCancelled) // true
    }

}

// After for a while, before download task finishes.
task?.cancel()

If the task already finished when you call task?.cancel(), nothing will happen.

Similar, the view extension methods also return DownloadTask. You can store and cancel it:

let task = imageView.kf.set(with: url)
task?.cancel()

Or, you can call cancelDownloadTask on the image view to cancel the current downloading task:

let task1 = imageView.kf.set(with: url1)
let task2 = imageView.kf.set(with: url2)

imageView.kf.cancelDownloadTask()
// `task2` will be cancelled, but `task1` is still running. 
// However, the downloaded image for `task1` will not be set because the image view expects a result from `url2`.

Authentication with NSURLCredential

The ImageDownloader uses a default behavior (.performDefaultHandling) when receives a challenge from server. If you need to provide your own credentials, setup an authenticationChallengeResponder:

// In ViewController
ImageDownloader.default.authenticationChallengeResponder = self

extension ViewController: AuthenticationChallengeResponsable {

    var disposition: URLSession.AuthChallengeDisposition { /* */ }
    let credential: URLCredential? { /* */ }

    func downloader(
        _ downloader: ImageDownloader,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
    {
        // Provide your `AuthChallengeDisposition` and `URLCredential`
        completionHandler(disposition, credential)
    }

    func downloader(
        _ downloader: ImageDownloader,
        task: URLSessionTask,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
    {
        // Provide your `AuthChallengeDisposition` and `URLCredential`
        completionHandler(disposition, credential)
    }
}

Download timeout

By default, the download timeout for a request is 15 seconds. To set it for the downloader:

// Set timeout to 1 minute.
downloader.downloadTimeout = 60

To define a timeout for a certain request, use a .requestModifier:

let modifier = AnyModifier { request in
    var r = request
    r.timeoutInterval = 60
    return r
}
downloader.downloadImage(with: url, options: [.requestModifier(modifier)])

Processor

ImageProcessor transforms an image (or data) to another image. You can provide a processor to ImageDownloader to apply it to the downloaded data. Then processed image will be sent to the image view and the cache.

Use the default processor

// Just without anything
imageView.kf.setImage(with: url)
// It equals to
imageView.kf.setImage(with: url, options: [.processor(DefaultImageProcessor.default)])

DefaultImageProcessor converts downloaded data to a corresponded image object. PNG, JPEG, and GIF are supported.

Built-in processors

// Round corner
let processor = RoundCornerImageProcessor(cornerRadius: 20)

// Downsampling
let processor = DownsamplingImageProcessor(size: CGSize(width: 100, height: 100))

// Cropping
let processor = CroppingImageProcessor(size: CGSize(width: 100, height: 100), anchor: CGPoint(x: 0.5, y: 0.5))

// Blur
let processor = BlurImageProcessor(blurRadius: 5.0)

// Overlay with a color & fraction
let processor = OverlayImageProcessor(overlay: .red, fraction: 0.7)

// Tint with a color
let processor = TintImageProcessor(tint: .blue)

// Adjust color
let processor = ColorControlsProcessor(brightness: 1.0, contrast: 0.7, saturation: 1.1, inputEV: 0.7)

// Black & White
let processor = BlackWhiteProcessor()

// Blend (iOS)
let processor = BlendImageProcessor(blendMode: .darken, alpha: 1.0, backgroundColor: .lightGray)

// Compositing
let processor = CompositingImageProcessor(compositingOperation: .darken, alpha: 1.0, backgroundColor: .lightGray)

// Use the process in view extension methods.
imageView.kf.setImage(with: url, options: [.processor(processor)])

Multiple processors

// First blur the image, then make it round cornered.
let processor = BlurImageProcessor(blurRadius: 4) >> RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [.processor(processor)])

Creating your own processor

Make a type conforming to ImageProcessor by implementing identifier and process:

struct WebpProcessor: ImageProcessor {

    // `identifier` should be the same for processors with the same properties/functionality
    // It will be used when storing and retrieving the image to/from cache.
    let identifier = "com.yourdomain.webpprocessor"
    
    // Convert input data/image to target image and return it.
    func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> Image? {
        switch item {
        case .image(let image):
            print("already an image")
            return image
        case .data(let data):
            return WebpFramework.createImage(from: webpData)
        }
    }
}

// Then pass it to the `setImage` methods:
let processor = WebpProcessor()
let url = URL(string: "https://example.com/image.webp")
imageView.kf.setImage(with: url, options: [.processor(processor)])

WebpFramework is an external framework you may use to parse WebP data. Currently, Kingfisher does not support WebP image out of the box.

Creating a processor from CIFilter

If you have a prepared CIFilter, you can create a processor quickly from it.

struct MyCIFilter: CIImageProcessor {

    let identifier = "com.yourdomain.myCIFilter"
    
    let filter = Filter { input in
        guard let filter = CIFilter(name: "xxx") else { return nil }
        filter.setValue(input, forKey: kCIInputBackgroundImageKey)
        return filter.outputImage
    }
}

Serializer

CacheSerializer will be used to convert some data to an image object for retrieving from disk cache and vice versa for storing to the disk cache.

Use the default serializer

// Just without anything
imageView.kf.setImage(with: url)
// It equals to
imageView.kf.setImage(with: url, options: [.cacheSerializer(DefaultCacheSerializer.default)])

DefaultCacheSerializer converts cached data to a corresponded image object and vice versa. PNG, JPEG, and GIF are supported by default.

Serializer to force a format

To specify a certain format an image should be, use FormatIndicatedCacheSerializer. It provides serializers for all built-in supported format: FormatIndicatedCacheSerializer.png, FormatIndicatedCacheSerializer.jpeg and FormatIndicatedCacheSerializer.gif.

By using the DefaultCacheSerializer, Kingfisher will respect the input image data format and try to keep it unchanged. However, there are some exceptions. A common case is that when you using a RoundCornerImageProcessor, you may always want the alpha channel (for the corner part). If your original image is JPEG, you may want to set the png serializer instead:

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

Creating your own serializer

Make a type conforming to CacheSerializer by implementing data(with:original:) and image(with:options:):

struct WebpCacheSerializer: CacheSerializer {
    func data(with image: Image, original: Data?) -> Data? {
        return WebpFramework.webpData(of: image)
    }
    
    func image(with data: Data, options: KingfisherParsedOptionsInfo?) -> Image? {
        return WebpFramework.createImage(from: webpData)
    }
}

// Then pass it to the `setImage` methods:
let serializer = WebpCacheSerializer()
let url = URL(string: "https://yourdomain.com/example.webp")
imageView.kf.setImage(with: url, options: [.cacheSerializer(serializer)])

Prefetch

You could prefetch some images and cache them before you display them on the screen. This is useful when you know a list of image resources you know they would probably be shown later.

let urls = ["https://example.com/image1.jpg", "https://example.com/image2.jpg"]
           .map { URL(string: $0)! }
let prefetcher = ImagePrefetcher(urls: urls) {
    skippedResources, failedResources, completedResources in
    print("These resources are prefetched: \(completedResources)")
}
prefetcher.start()

// Later when you need to display these images:
imageView.kf.setImage(with: urls[0])
anotherImageView.kf.setImage(with: urls[1])

Using ImagePrefetcher with UICollectionView or UITableView

From iOS 10, Apple introduced a cell prefetching behavior. It could work seamlessly with Kingfisher's ImagePrefetcher.

override func viewDidLoad() {
    super.viewDidLoad()
    collectionView?.prefetchDataSource = self
}

extension ViewController: UICollectionViewDataSourcePrefetching {
    func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
        let urls = indexPaths.flatMap { URL(string: $0.urlString) }
        ImagePrefetcher(urls: urls).start()
    }
}

See WWDC 16 - Session 219 for more about changing of it in iOS 10.

ImageDataProvider

Instead of downloading an image from the network, Kingfisher also supports to set image from a data provider. This is useful when you have local data for images. At the same time, you want to use Kingfisher's options to process and manage them.

Image from local file

LocalFileImageDataProvider is a type conforming to ImageDataProvider. It is used to load an image from a local file URL:

let url = URL(fileURLWithPath: path)
let provider = LocalFileImageDataProvider(fileURL: url)
imageView.kf.setImage(with: provider)

You can also pass options to it:

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

Creating your own image data provider

In Kingfisher, we also have a Base64ImageDataProvider to load an image from a Base64 string and a general purpose RawImageDataProvider to provide an image from any raw data.

If you want to create your own image data provider type, conform to ImageDataProvider protocol by implementing a cacheKey and a data(handler:) method to provide image data:

struct UserNameLetterIconImageProvider: ImageDataProvider {
    var cacheKey: String { return letter }
    let letter: String
    
    init(userNameFirstLetter: String) {
        self.letter = userNameFirstLetter
    }
    
    func data(handler: @escaping (Result<Data, Error>) -> Void) {
        
        // You can ignore these detail below.
        // It generates some data for an image with `letter` being rendered in the center.

        let letter = self.letter as NSString
        let rect = CGRect(x: 0, y: 0, width: 250, height: 250)
        let renderer = UIGraphicsImageRenderer(size: rect.size)
        let data = renderer.pngData { context in
            UIColor.black.setFill()
            context.fill(rect)
            
            let attributes = [
                NSAttributedString.Key.foregroundColor: UIColor.white,
                                      .font: UIFont.systemFont(ofSize: 200)
            ]
            
            let textSize = letter.size(withAttributes: attributes)
            let textRect = CGRect(
                x: (rect.width - textSize.width) / 2,
                y: (rect.height - textSize.height) / 2,
                width: textSize.width,
                height: textSize.height)
            letter.draw(in: textRect, withAttributes: attributes)
        }

        // Provide the image data in `handler`.
        handler(.success(data))
    }
}

// Set image for user "John"
let provider = UserNameLetterIconImageProvider(userNameFirstLetter: "J")
imageView.kf.setImage(with: provider)

Maybe you have already noticed, the data(handler:) contains a callback to you. You can provide the image data in an asynchronous way from another thread if it is too heavy in the main thread.

Indicator

Using an image as the indicator

let path = Bundle.main.path(forResource: "loader", ofType: "gif")!
let data = try! Data(contentsOf: URL(fileURLWithPath: path))

imageView.kf.indicatorType = .image(imageData: data)
imageView.kf.setImage(with: url)

Using a customized view

struct MyIndicator: Indicator {
    let view: UIView = UIView()
    
    func startAnimatingView() { view.isHidden = false }
    func stopAnimatingView() { view.isHidden = true }
    
    init() {
        view.backgroundColor = .red
    }
}

let i = MyIndicator()
imageView.kf.indicatorType = .custom(indicator: i)

Updating your own indicator with percentage

imageView.kf.setImage(with: url, progressBlock: {
    receivedSize, totalSize in
    let percentage = (Float(receivedSize) / Float(totalSize)) * 100.0
    print("downloading progress: \(percentage)%")
    myIndicator.percentage = percentage
})

The progressBlock will be only called if your server response contains the "Content-Length" in the header.

Other options

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 is not existing in the cache, a .cacheError with .imageNotExisting reason will be raised.

Loading disk file synchronously

By default, all operations, including disk cache loading, are asynchronous for performance. Sometimes, you may want to reload an image (in a table view, for example). If the cached images only exist in the disk cache, you will find a flickering. That is because the image setting is dispatched to an I/O queue first. When the image loading/processing finished, it is dispatched back to the main queue asynchronously.

To change this behavior and get rid of the flickering, you can pass .loadDiskFileSynchronously when reloading:

let options: [KingfisherOptionsInfo]? = isReloading ? [.loadDiskFileSynchronously] : nil
imageView.kf.set(with: image, options: options)

Waiting for cache finishing

Storing images to disk cache is an asynchronous operation. However, it is not required to be done before we can set the image view and call the completion handler in view extension methods. In other words, the disk cache may not exist yet in the completion handler 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
        }
    }
}

This is not a problem for most use cases. However, if your logic depends on the existing of disk cache, pass .waitForCache as an option. Kingfisher will then wait until the disk cache finishes before calling the handler:

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 is only for disk image cache, which involves to async I/O. For the memory cache, everything goes synchronously, so the image should be always in the memory cache.

For UIButton & NSButton

let uiButton: UIButton = //...
uiButton.kf.setImage(with: url, for: .normal)
uiButton.kf.setBackgroundImage(with: url, for: .normal)

let nsButton: NSButton = //...
nsButton.kf.setImage(with: url)
nsButton.kf.setAlternateImage(with: url)

Animated Images

Kingfisher supports to display GIF images.

Loading a GIF

let imageView: UIImageView = ...
imageView.kf.setImage(with: URL(string: "your_animated_gif_image_url")!)

If you encountered to memory issues when dealing with large GIF, try to use AnimatedImageView instead of regular image view to display GIF. It will only decode several frames of your GIF image to get a smaller memory footprint (but high CPU load).

let imageView = AnimatedImageView()
imageView.kf.setImage(with: URL(string: "your_large_animated_gif_image_url")!)

Only load the first frame from GIF

imageView.kf.setImage(
    with: URL(string: "your_animated_gif_image_url")!, 
    options: [.onlyLoadFirstFrame])

It will be useful when you just want to display a static preview of the first frame from a GIF image.

Performance Tips

Cancelling unnecessary downloading tasks

Once a downloading task initialized, even when you set another URL to the image view, that task will continue until finishes.

imageView.kf.setImage(with: url1) { result in 
    // `result` is `.failure(.imageSettingError(.notCurrentSourceTask))`
    // But the download (and cache) is done.
}

// Set again immediately.
imageView.kf.setImage(with: url2) { result in 
    // `result` is `.success`
}

Although setting for url1 results in a .failure since the setting task was overridden by url2, the download task itself is finished. The downloaded image data is also processed and cached.

The downloading and caching operation for the image at url1 is not free, it costs network, CPU time, memory and also, battery.

In most cases, it worths to do that. Since there is a chance that the image is shown to the user again. But if you are sure that you do not need the image from url1, you can cancel the downloading before starting another one:

imageView.kf.setImage(with: ImageLoader.sampleImageURLs[8]) { result in
    // `result` is `.failure(.requestError(.taskCancelled))`
    // Now the download task is cancelled.
}

imageView.kf.cancelDownloadTask()
imageView.kf.setImage(with: ImageLoader.sampleImageURLs[9]) { result in
    // `result` is `.success`
}

This technology sometimes is useful in a table view or collection view. When users scrolling the list fast, maybe quite a lot of image downloading tasks would be created. You can cancel unnecessary tasks in the didEndDisplaying delegate method:

func collectionView(
    _ collectionView: UICollectionView,
    didEndDisplaying cell: UICollectionViewCell,
    forItemAt indexPath: IndexPath)
{
    // This will cancel the unfinished downloading task when the cell disappearing.
    cell.imageView.kf.cancelDownloadTask()
}

Using processor with ImageCache

Kingfisher is smart enough to cache the processed images and then get it back if you specify the correct ImageProcessor in the option. Each ImageProcessor contains an identifier. It is used when caching the processed images.

Without the identifier, Kingfisher will not be able to tell which is the correct image in cache. Think about the case you have to store two versions of an image from the same url, one should be round cornered and another should be blurred. You need two different cache keys. In all Kingfisher's built-in image processors, the identifier will be determined by the kind of processor, combined with its parameters for each instance. For example, a round corner processor with 20 as its corner radius might have an identifier as round-corner-20, while a 40 radius one's could be round-corner-40. (Just for demonstrating, they are not that simple value in real)

So, when you create your own processor, you need to make sure that you provide a different identifier for any different processor instance, with its parameter considered. This helps the processors work well with the cache. Furthermore, it prevents unnecessary downloading and processing.

Cache original image when using a processor

If you are trying to do one of these:

  1. Process the same image with different processors to get different versions of the image.
  2. Process an image with a processor other than the default one, and later need to display the original image.

It worths passing .cacheOriginalImage as an option. This will store the original downloaded image to cache as well:

let p1 = MyProcessor()
imageView.kf.setImage(with: url, options: [.processor(p1), .cacheOriginalImage])

Both the processed image by p1 and the original downloaded image will be cached. Later, when you process with another processor:

let p2 = AnotherProcessor()
imageView.kf.setImage(with: url, options: [.processor(p2)])

The processed image for p2 is not in cache yet, but Kingfisher now has a chance to check whether the original image for url being in cache or not. Instead of downloading the image again, Kingfisher will reuse the original image and then apply p2 on it directly.

Using DownsamplingImageProcessor for high resolution images

Think about the case we want to show some large images in a table view or a collection view. In the ideal world, we expect to get smaller thumbnails for them, to reduce downloading time and memory use. But in the real world, maybe your server doesn't prepare such a thumbnail version for you. The newly added DownsamplingImageProcessor rescues. It downsamples the high-resolution images to a certain size before loading to memory:

imageView.kf.setImage(
    with: resource,
    placeholder: placeholderImage,
    options: [
        .processor(DownsamplingImageProcessor(size: imageView.size)),
        .scaleFactor(UIScreen.main.scale),
        .cacheOriginalImage
    ])

Typically, DownsamplingImageProcessor is used with .scaleFactor and .cacheOriginalImage. It provides a reasonable image pixel scale for your UI, and prevent future downloading by caching the original high-resolution image.