Thinking and feeling of the first reading of Kingfisher network image cache Library

Keywords: Mobile Swift network xcode github

The OC version of SDWebimage that I read before is mature due to the development trend of Swift. It's also time to practice (think of Andy Lau's practice), think, read and write while flying in the world.

At first, I plan to read it from the beginning, but the Kingfisher version is limited to upgrading with the Swift version, so if you want to read the original version, you need to download Xcode 8 or even the previous Xcode version. The syntax from 2.0 to 3.0 has changed a lot, so I started reading it directly from 3.0. The download is Xcode 9. You can set version 3.2 as a minimum, but it doesn't matter. After 3.0, the API changes a little. Download the minimum version by yourself.

Enter the main topic:

3.0 original source address: https://github.com/onevcat/Kingfisher/tree/3.0.0-beta.1

Let's think about using Swift syntax to write a control to load the network image display:

First, create a CollectionView control with Main.storyboard and put a picture on it in the cell, then return several more cells, then create a classification similar to oc, and write the steps commonly used to convert url to image into classification. Each time you use this kind of control to load network pictures, you can do it in a way that is not so complicated. Simplest implementation:

	import UIKit

class ViewController: UICollectionViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    //Here is the proxy method of collectionView. I think someone wrote an extension. Why? Can't you write a book?
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell:CollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell

        // Initialize url picture
        let url:URL = URL.init(string:"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1572933973277&di=dfcdb7e4aa6339a22ca5e40270987790&imgtype=0&src=http%3A%2F%2Fpic44.nipic.com%2F20140723%2F18505720_094503373000_2.jpg")!
        cell.cellImageView.sfsc_setImage(url: url)
        return cell
    }
}

import UIKit

class CollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var cellImageView: UIImageView!
}

///Here is the image loading code 

import Foundation
import UIKit
///Used by typealias to redefine the name of an existing type.
typealias ImageView = UIImageView
extension ImageView {
    func sfsc_setImage(url:URL) -> Void {
        //-------This part of the code is used for every network image, so it is extracted---------
        //Convert to data type
        let data : NSData! = NSData(contentsOf: url)
        //Judge that the data is not empty, because swift is very strict with the type. If it is not empty, it will crash
        if data != nil {
            //Assignment picture
            self.image = UIImage.init(data: data as Data, scale: 1)
        }else{
            // Otherwise, the default image will be assigned
            self.image = UIImage.init(named: "005")
        }
    }
}

A simple image loading is implemented. Next, let's see how Kingfisher's author implements it:

// MARK: - Set Images
/**
*	Set image to use from web.
*/
extension ImageView {

    /**
    Set an image with a resource, a placeholder image, options, progress handler and completion handler.
    
    - parameter resource:          Resource object contains information such as `cacheKey` and `downloadURL`.
    - parameter placeholder:       A placeholder image when retrieving the image at URL.
    - parameter options:           A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
    - parameter progressBlock:     Called when the image downloading progress gets updated.
    - parameter completionHandler: Called when the image retrieved and set.
    
    - returns: A task represents the retrieving process.
     
    - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread. 
     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
	 //Here are the translation notes:
	 / * *
Use resources, placeholder images, options, progress handlers, and finish handlers to set up images.

-Parameter resource: the resource object contains information such as' cacheKey 'and' downloadURL '.
-Parameter placeholder: a placeholder image retrieves the image at the URL.
-Parameter options: a dictionary can control some behaviors. For more information, see "Kingfisher choose sinfo.".
-Parameter progressBlock: called when the image download progress is updated.
-Parameter completionHandler: image retrieval and settings at call time.

-Return: the task represents the retrieval process.

-Note: 'progressBlock' and 'completionHandler' will be called in the main thread.
' optionsInfo 'The 'CallbackDispatchQueue' specified in will not be used in this method's callback.
* /
    */
	/*
	swift If a normal method has a return value, there must be a receiver when it is called. Otherwise, the compiler will give a warning. If @ discardableResult is added before the method, there will be no warning. You can also use a wildcard to receive the return value of the method, which can achieve the same purpose.
	*/
    @discardableResult
    public func kf_setImage(with resource: Resource?,
                              placeholder: Image? = nil,
                                  options: KingfisherOptionsInfo? = nil,
                            progressBlock: DownloadProgressBlock? = nil,
                        completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
    {
        image = placeholder
        
        guard let resource = resource else {
            completionHandler?(nil, nil, .none, nil)
            return .empty
        }
        
        let showIndicatorWhenLoading = kf_showIndicatorWhenLoading
        var indicator: IndicatorView? = nil
        if showIndicatorWhenLoading {
            indicator = kf_indicator
            indicator?.isHidden = false
            indicator?.kf_startAnimating()
        }
        
        kf_setWebURL(resource.downloadURL)
        
        var options = options ?? KingfisherEmptyOptionsInfo
        if shouldPreloadAllGIF() {
            options.append(.preloadAllGIFData)
        }

        let task = KingfisherManager.shared.retrieveImage(with: resource, options: options,
            progressBlock: { receivedSize, totalSize in
                if let progressBlock = progressBlock {
                    progressBlock(receivedSize, totalSize)
                }
            },
            completionHandler: {[weak self] image, error, cacheType, imageURL in
                
                DispatchQueue.main.safeAsync {
                    guard let sSelf = self, imageURL == sSelf.kf_webURL else {
                        return
                    }
                    
                    sSelf.kf_setImageTask(nil)
                    
                    guard let image = image else {
                        indicator?.kf_stopAnimating()
                        completionHandler?(nil, error, cacheType, imageURL)
                        return
                    }
                    
                    guard let transitionItem = options.kf_firstMatchIgnoringAssociatedValue(.transition(.none)),
                        case .transition(let transition) = transitionItem, ( options.forceTransition || cacheType == .none) else
                    {
                        indicator?.kf_stopAnimating()
                        sSelf.image = image
                        completionHandler?(image, error, cacheType, imageURL)
                        return
                    }
                    
                    #if !os(macOS)
                    UIView.transition(with: sSelf, duration: 0.0, options: [],
                        animations: { indicator?.kf_stopAnimating() },
                        completion: { _ in
                            UIView.transition(with: sSelf, duration: transition.duration,
                                options: [transition.animationOptions, .allowUserInteraction],
                                animations: {
                                    // Set image property in the animation.
                                    transition.animations?(sSelf, image)
                                },
                                completion: { finished in
                                    transition.completion?(finished)
                                    completionHandler?(image, error, cacheType, imageURL)
                                }
                            )
                    })
                    #endif
                }
            })
        
        kf_setImageTask(task)
        
        return task
    }
}

It can be seen that all aspects of code processing are relatively comprehensive. We can not only display the loading, but also ignore the bitmap occupation, loading progress and closure. From the simple processing and caching, we can make a comparison with the most basic SDWebImage before OC:

First, learn a simple judgment from the above code:

 // Guard let syntax: after the guard let judges, the guard must have a value if no value is returned directly in {} of the guard let.
 guard let resource = resource else {
            completionHandler?(nil, nil, .none, nil)
            return .empty
        }

Improve your approach:

    func sfsc_setImage(url:URL) -> Void {
        //-------This part of the code is used for every network image, so it is extracted---------
        /* guard let Syntax: after judging the guard let, the guard must have a value if no value is returned directly in {} of the guard let.
          If you need to guard multiple values, separate them with commas.
         
         */
        guard let url = url else {
            return
        }
        //Code execution to this url must have a value!!
        //Convert to data type
        let data : NSData! = NSData(contentsOf: url)
        //Judge that the data is not empty, because swift is very strict with the type. If it is not empty, it will crash
        if data != nil {
            //Assignment picture
            self.image = UIImage.init(data: data as Data, scale: 1)
        }else{
            // Otherwise, the default image will be assigned
            self.image = UIImage.init(named: "005")
        }
    }

public func kf_setImage... For the time being, I will not add public if I write it myself.

import Foundation


/// `Resource` protocol defines how to download and cache a resource from network.
public protocol Resource {
    /// The key used in cache.
    var cacheKey: String { get }
    
    /// The target image URL.
    var downloadURL: URL { get }
}

/**
 ImageResource is a simple combination of `downloadURL` and `cacheKey`.
 
 When passed to image view set methods, Kingfisher will try to download the target 
 image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache.
 */
public struct ImageResource: Resource {
    /// The key used in cache.
    public let cacheKey: String
    
    /// The target image URL.
    public let downloadURL: URL
    
    /**
     Create a resource.
     
     - parameter downloadURL: The target image URL.
     - parameter cacheKey:    The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key.
     
     - returns: A resource.
     */
    public init(downloadURL: URL, cacheKey: String? = nil) {
        self.downloadURL = downloadURL
        self.cacheKey = cacheKey ?? downloadURL.absoluteString
    }
}

/**
 URL conforms to `Resource` in Kingfisher.
 The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`.
 If you need customize the url and/or cache key, use `ImageResource` instead.
 */
extension URL: Resource {
    public var cacheKey: String { return absoluteString }
    public var downloadURL: URL { return self }
}

Think about why the protocol is used here?

Posted by gli on Tue, 05 Nov 2019 19:55:52 -0800