Kingfisher source code analysis series, because the level is limited, where is wrong, please kindly comment

  • Use of Kingfisher source code parsing
  • Kingfisher source code parsing Options explanation
  • Kingfisher source code parsing loading process
  • Kingfisher source code parsing loading GIF
  • Kingfisher source code parsing ImageCache
  • Kingfisher Source code Processor and CacheSerializer
  • Kingfisher source code parsing ImagePrefetcher

1. Basic use

1.1 Setting Images using Resource

The built-in ImageResource and URL in Kingfisher implement the Resource protocol. The difference between ImageResource and URL is that ImageResource has a custom cacheKey.

  • URL Settings image
let url = URL(string: "https://test/image.jpg")!
imageView.kf.setImage(with: url)
Copy the code
  • ImageResource
let imageResource = ImageResource(downloadURL: url, cacheKey: "custom_cache_key")
imageView.kf.setImage(with: imageResource)
Copy the code
1.2 Setting an image using the ImageDataProvider

Kingfisher with built-in LocalFileImageDataProvider Base64ImageDataProvider, three ImageDataProvider RawImageDataProvider.

  • LocalFileImageDataProvider
let fileUrl = Bundle.main.url(forResource: "image", withExtension: "jpg")!
let imageDataProvider =  LocalFileImageDataProvider(fileURL: fileUrl)
imageView.kf.setImage(with: imageDataProvider)
Copy the code
  • Base64ImageDataProvider
let base64String = "..."
let base64ImageDataProvider = Base64ImageDataProvider(base64String: base64String, cacheKey: "base64_cache_key")
imageView.kf.setImage(with: base64ImageDataProvider)
Copy the code
  • RawImageDataProvider
let data = Data()
let dataImageDataProvider = RawImageDataProvider(data: data, cacheKey: "data_cache_key")
imageView.kf.setImage(with: dataImageDataProvider)
Copy the code
  • Custom ImageDataProvider
/ / define public struct FileNameImageDataProvider: ImageDataProvider {publiclet cacheKey: String
    public let fileName: String
    public init(fileName: String, cacheKey: String? = nil) {
        self.fileName = fileName
        self.cacheKey = cacheKey ?? fileName
    }
    
    public func data(handler: @escaping (Result<Data, Error>) -> Void) {
        if let fileURL = Bundle.main.url(forResource: fileName, withExtension: "") {
            handler(Result(catching: { try Data(contentsOf: fileURL) }))
        }else {
            let error = NSError(domain: "File does not exist", code: -1, userInfo: ["fileName":fileName]) handler(.failure(error))}}} // uselet fileNameImageDataProvider = FileNameImageDataProvider(fileName: "image.jpg")
imageView.kf.setImage(with: fileNameImageDataProvider)
Copy the code
1.3 show the placeholder
  • Set the placeholder with UIImage
let placeholderImage = UIImage(named: "placeholder.png")
imageView.kf.setImage(with: url, placeholder: placeholderImage)
Copy the code
  • Set the placeholder with a custom View
PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView {// PlaceholderView { UIView, Placeholder {} // uselet placeholderView = PlaceholderView()
imageView.kf.setImage(with: url, placeholder: placeholderView)
Copy the code
1.4 Loading a GIF Image
  • Load giFs via UIImageView
let url = URL(string: "https://test/image.gif")!
imageView.kf.setImage(with: url)
Copy the code
  • Load the GIF through AnimatedImageView
let url = URL(string: "https://test/image.gif")!
animatedImageView.kf.setImage(with: url)
Copy the code

Please refer to the loading GIF of Kingfisher source code analysis for the differences between the two

1.5 Setting indicators
  • No indicator
imageView.kf.indicatorType = .none
Copy the code
  • Use UIActivityIndicatorView as the indicator
imageView.kf.indicatorType = .activity
Copy the code
  • Use pictures as indicators
let path = Bundle.main.path(forResource: "loader", ofType: "gif")!
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
imageView.kf.indicatorType = .image(imageData: data)
Copy the code
  • Use a custom View as an indicator
Struct CustomIndicator: Indicator {let view: UIView = UIView()
    func startAnimatingView() { view.isHidden = false }
    func stopAnimatingView() { view.isHidden = true }
    init() {view.backgroundcolor =.red}} // uselet indicator = CustomIndicator()
imageView.kf.indicatorType = .custom(indicator: indicator)
Copy the code
1.6 set the transition

Transition is used for displaying animations after images are loaded. There are the following types

Public enum ImageTransition {// No animationcaseNone / / equivalent to UIView. AnimationOptions. TransitionCrossDissolvecaseFade (TimeInterval) / / equivalent to UIView. AnimationOptions. TransitionFlipFromLeftcaseFlipFromLeft (TimeInterval) / / equivalent to UIView. AnimationOptions. TransitionFlipFromRightcaseFlipFromRight (TimeInterval) / / equivalent to UIView. AnimationOptions. TransitionFlipFromTopcaseFlipFromTop (TimeInterval) / / equivalent to UIView. AnimationOptions. TransitionFlipFromBottomcaseFlipFromBottom (TimeInterval) // Custom animationcasecustom(duration: TimeInterval, options: UIView.AnimationOptions, animations: ((UIImageView, UIImage) -> Void)? , completion: ((Bool) -> Void)?) }Copy the code

use

ImageView. Kf. SetImage (with: url, the options: [. The transition (. Fade (0.2))))Copy the code

2. Processor

2.1 DefaultImageProcessor

Convert the downloaded data to the corresponding UIImage. Supports PNG, JPEG and GIF formats.

2.2 BlendImageProcessor

Modify the image blend mode, the core implementation is as follows

  1. First, DefaultImageProcessor is used to convert Data into image, and then to draw
  2. Get context
  3. Fills the background color for the context
  4. Call the image.draw function to set the blending mode
  5. Get the image from the context as procesLovelage
  6. Release the context and return ProcesLast_Age
letImage = before processing UIGraphicsBeginImageContextWithOptions (size,false, scale)
let context = UIGraphicsGetCurrentContext()
let rect = CGRect(origin: .zero, size: size)
backgroundColor.setFill()
UIRectFill(rect)
image.draw(in: rect, blendMode: blendMode, alpha: alpha)
let cgImage = context.makeImage()
let processedImage = UIImage(cgImage: cgImage, scale: scale, orientation: image.orientation)
UIGraphicsEndImageContext()
return processedImage
Copy the code
2.3 OverlayImageProcessor

Add an overlay on the image, which is also a hybrid mode in essence, and the logic is roughly the same as above

2.4 BlurImageProcessor

Add gaussian blur to the picture, with Vimage

2.5 RoundCornerImageProcessor

Add rounded corners to the picture. The four corners can be combined as follows

/ / set up four corners rounded imageView. Kf. SetImage (with: url, the options: [. Processor (RoundCornerImageProcessor (cornerRadius: 20))) / / set up to the top corner and lower right rounded imageView. Kf. SetImage (with: url, the options: [. Processor (RoundCornerImageProcessor (cornerRadius: 20 ,roundingCorners: [.topLeft, .bottomRight]))])Copy the code

How to do it: Set a rounded rectangle with rounded corners using bezier curves and crop the image

letPath = UIBezierPath(roundedRect: rect, byRoundingCorners: corners. UiRectCorner,// Which corner is the cornerRadii: CGSize(width: radius, height: radius) ) context.addPath(path.cgPath) context.clip() image.draw(in: rect)
Copy the code
2.6 TintImageProcessor

Use color to give the main color to the image, which is realized by using the CIFilter in CoreImage. These two CIFilters (name: “CIConstantColorGenerator”) and CIFilter(name: “CISourceOverCompositing”)

2.7 ColorControlsProcessor

Modify the contrast, exposure, brightness and saturation of the image by using CIFilter in CoreImage, using these two CIColorControls and CIExposureAdjust

2.8 BlackWhiteProcessor

Grayscale the image, is a special case of ColorControlsProcessor

2.9 CroppingImageProcessor

Crop the picture

2.10 DownsamplingImageProcessor

Image sampling, generally in a small imageView to display a large HD image core implementation:

public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> KFCrossPlatformImage? {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
        return nil
    }
    
    let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
    let downsampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
        return nil
    }
    return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil)
}
Copy the code
2.11 GeneralProcessor

It is used to combine multiple existing processors in the following way. The GeneralProcessor is used

// Use mode 1let processor1 = BlurImageProcessor(blurRadius: 5)
let processor2 = RoundCornerImageProcessor(cornerRadius: 20)
let generalProcessor = GeneralProcessor(identifier: "123") { (item, options) -> KFCrossPlatformImage? in
   if let image = processor1.process(item: item, options: options) {
       return processor2.process(item: .image(image), options: options)
   }
   returnNil} // uses method 2, which is an extension of ProcessorletgeneralProcessor = BlurImageProcessor(blurRadius: 5).append(RoundCornerImageProcessor(cornerRadius: 20)_ // Use method 3, custom operator, call append methodlet generalProcessor = BlurImageProcessor(blurRadius: 5) |> RoundCornerImageProcessor(cornerRadius: 20)
Copy the code
2.12 Customizing processors

Refer to The Kingfisher source code Processor and CacheSerializer

3 the cache

3.1 Using a Customized cacheKey

Typically, images are loaded directly from a URL, in which case the cacheKey is url.absoluteString, or you can customize the cacheKey using ImageResource

3.2 Using the cacheKey to Determine whether a Cache is being created and the cache type

CacheType is an enumeration with three cases :.none is not cached,.memory is cached, and.disk is cached. CacheKey + Processor. identifier is the unique identifier of the cache, but the identifier of DefaultImageProcessor is an empty string. If a Processor other than DefaultImageProcessor is specified at load time, the processorIdentifier needs to be specified at lookup time

let cache = ImageCache.default
let isCached = cache.isCached(forKey: cacheKey)
let cacheType = cache.imageCachedType(forKey: cacheKey) // If Processor is specified, use this method to query cached cache.iscached (forKey: cacheKey, processorIdentifier: processor.identifier)
Copy the code
3.3 Retrieving images from the cache using the cacheKey
cache.retrieveImage(forKey: "cacheKey") { result in
    switch result {
    case .success(let value):
        print(value.cacheType)
        print(value.image)
    case .failure(let error):
        print(error)
    }
}
Copy the code
3.4 Setting the Cache Configuration
3.4.1 Setting the Memory Cache Capacity Limit (The default value is 1/4 of the physical memory)
cache.memoryStorage.config.totalCostLimit = 100 * 1024 * 1024
Copy the code
3.4.2 Setting the Maximum number of Memory caches
cache.memoryStorage.config.countLimit = 150
Copy the code
3.4.3 Setting the Expiration Time of the Memory Cache (Default: 300 seconds)
cache.memoryStorage.config.expiration = .seconds(300)
Copy the code

You can also specify the memory cache for a particular image

imageView.kf.setImage(with: url, options:[.memoryCacheExpiration(.never)])
Copy the code
3.4.4 Setting the Memory Cache expiration Update Policy

Update policy is an enumeration with three cases,.None expiration time is not updated,.cacheTime adds expiration time to the current time,.expirationTime(_ expiration: StorageExpiration) expiration time is updated to a specified expiration time. The default value is. CacheTime, which can be used as follows

imageView.kf.setImage(with: url, options:[.memoryCacheAccessExtendingExpiration(.cacheTime)])
Copy the code
3.4.5 Setting the interval for memory cache to clear expired memory (this value is immutable and can only be assigned at initialization)
cache.memoryStorage.config.cleanInterval = 120
Copy the code
3.4.6 Setting the Disk Cache Capacity
cache.diskStorage.config.sizeLimit =  = 500 * 1024 * 1024
Copy the code
3.4.7 Setting the expiration time and update policy for disk Cache

Same memory cache

3.5 Manually Caching images
// Common cachelet image: UIImage = //...
cache.store(image, forKey: cacheKey) // Cache raw datalet data: Data = //...
let image: UIImage = //...
cache.store(image, original: data, forKey: cacheKey)
Copy the code
3.6 Clearing the Cache
3.6.1 Deleting the specified Cache
ForKey: cacheKey, processorIdentifier: processor. Identifier, fromMemory: false, True // Whether to delete from disk cache){}Copy the code
3.6.2 Clearing the Memory Cache To clear expired memory cache
/ / empty the memory cache cache. ClearMemoryCache () / / clear the in-memory cache expiration cache. CleanExpiredMemoryCache ()Copy the code
3.6.3 Clearing the Disk Cache: Clear the expired disk cache and the cache that exceeds the disk capacity limit
/ / clear disk cache cache. ClearDiskCache () / / clear expired disk cache and cache cache exceeds the disk capacity limit. CleanExpiredDiskCache ()Copy the code
3.7 Obtaining the Disk Cache Size
cache.calculateDiskStorageSize()
Copy the code

5. Download

5.1 Manually Downloading Images
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)
    }
}
Copy the code
5.2 Modifying the Request before sending the Request
// Define a requestModifierlet modifier = AnyModifier { request in
    var r = request
    r.setValue("abc".forHTTPHeaderField: "Access-Token")
    returnR} // Set downloader.downloadImage(with: URL, options: [.requestModifier(modifier)]) {} // In imageViewsetSetImage (with: url, options: [.requestModifier(modifier)])Copy the code
5.3 Setting the Timeout Period
downloader.downloadTimeout = 60
Copy the code
5.4 Handling Redirection
// Define a redirection processing logiclet anyRedirectHandler = AnyRedirectHandler { (task, resp, req, completionHandler) inCompletionHandler (req)} // Set downloader.downloadImage(with: URL, options: [.reDirecthandler (anyRedirectHandler)]) // In imageViewsetSetImage (with: url, options: [.redirecthandler (anyRedirectHandler)])Copy the code
5.5 Canceling download
// Cancel manual downloadlet task = downloader.downloadImage(with: url) { result in} task? .cancel() // Cancel the imageView downloadlettask = imageView.kf.set(with: url) task? .cancel()Copy the code

6. Preloading

See Kingfisher source code for ImagePrefetcher

let urls = ["https://example.com/image1.jpg"."https://example.com/image2.jpg"]
           .map { URL(string: $0)! }
let prefetcher = ImagePrefetcher(urls: urls)
prefetcher.start()
Copy the code

7. Some useful options

  • Whether loadDiskFileSynchronously loaded from the disk, the synchronized to load
  • OnlyFromCache is loaded only from the cache
  • CacheMemoryOnly Specifies whether to use memory cache only
  • ForceRefresh Specifies whether to force a refresh. If true, it will be redownloaded each time
  • BackgroundDecode whether to decode in child threads
  • . For other configurations, please refer to Options in Kingfisher source Code