第一次讀Kingfisher網絡圖片緩存庫的思考與感覺

以前閱讀的OC版本的SDWebimage,因爲Swift發展趨勢已經成熟,也是時候練習練習了(想起了劉德華的練習),邊思考邊閱讀邊寫吧,翱翔在這個世界裏。git

一開始我打算從最初的開始閱讀的,可是限於Kingfisher版本跟隨Swift 版本一塊兒升級,因此若是想閱讀最初的就要下載Xcode 8 甚至更前面的Xcode版本。 2.0到3.0的語法變化比較大,因此我直接從3.0開始讀了,下載的是Xcode 9版本,最低能夠設置 3.2版本,可是沒有關係,3.0之後 API變更就小了。下載最低版本請自行搜索。github

進入正題:web

3.0最第一版本源碼地址:https://github.com/onevcat/Kingfisher/tree/3.0.0-beta.1swift

先來思考一下用Swift語法來寫一個 控件加載網絡圖片顯示:緩存

先用 Main.storyboard 建立一個 CollectionView控件 上面放一張圖片在cell裏,而後多返回幾個cell ,而後建立一個 相似oc的分類,把url轉換到image經常使用的步驟寫到分類裏,每次用到該類的控件加載網絡圖片就能夠一個方法搞定 不用那麼複雜。最簡單的實現:網絡

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.
    }
    
    //這裏collectionView的代理方法我看有人寫個擴展,爲何呢?寫本類不行麼?
    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

        // 初始化url圖片
        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!
}

///如下是圖片加載代碼 

import Foundation
import UIKit
/// typealias用來爲已存在的類型從新定義名稱的。
typealias ImageView = UIImageView
extension ImageView {
    func sfsc_setImage(url:URL) -> Void {
        //------- 這部分代碼,每一個須要網絡圖片的都會用到,因此提取出來 ---------
        //轉爲data類型
        let data : NSData! = NSData(contentsOf: url)
        //判斷data不爲空,這裏是由於swift對類型要求很嚴,若是未空的話,會崩潰
        if data != nil {
            //賦值圖片
            self.image = UIImage.init(data: data as Data, scale: 1)
        }else{
            // 不然就賦值默認圖片
            self.image = UIImage.init(named: "005")
        }
    }
}

一個簡單的圖片加載就實現了,接下來咱們來看看Kingfisher的做者是怎麼實現的吧:閉包

// 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.
	 //下面是翻譯註釋:
	 / * *
使用資源、佔位符圖像、選項、進度處理程序和完成處理程序設置圖像。

-參數資源:資源對象包含' cacheKey '和' downloadURL '等信息。
-參數佔位符:一個佔位符圖像檢索圖像在URL。
-參數選項:一個字典能夠控制一些行爲。更多信息請參見「翠鳥選擇sinfo」。
-參數progressBlock:當圖像下載進度更新時調用。
-參數completionHandler:調用時的圖像檢索和設置。

-返回:任務表示檢索過程。

-注意:' progressBlock '和' completionHandler '將在主線程中被調用。
' optionsInfo '中指定的' CallbackDispatchQueue '將不會在此方法的回調中使用。
* /
    */
	/*
	swift正常的方法若是有返回值的話,調用的時候必須有一個接收方,不然的話編譯器會報一個警告,若是在方法前加上 @discardableResult 不處理的時候就不會	有警告了。也能夠用一個通配符接收方法返回值,能夠達到一樣的目的。
	*/
    @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
    }
}

能夠看出代碼處理各方面仍是比較全面的,不僅僅是咱們這樣僅僅顯示加載就能夠,咱們先從簡單一點點來,先忽略掉 佔位圖 和 加載進度還有 閉包這些,單從簡單的處理和緩存看起,這樣能夠和OC以前的最基礎的SDWebImage作個對照:app

先從上面的代碼學一個簡單的判斷:ide

// guard let語法:guard let 判斷以後 守護 必定有值 若是沒有值 在guard let 的{} 裏 直接返回。
 guard let resource = resource else {
            completionHandler?(nil, nil, .none, nil)
            return .empty
        }

完善本身的方法:this

func sfsc_setImage(url:URL) -> Void {
        //------- 這部分代碼,每一個須要網絡圖片的都會用到,因此提取出來 ---------
        /* guard let語法:guard let 判斷以後 守護 必定有值 若是沒有值 在guard let 的{} 裏 直接返回。
          若是須要守護多個值直接在後面跟 逗號 分割開來。
         
         */
        guard let url = url else {
            return
        }
        //代碼執行至此 url 必定有值!!
        //轉爲data類型
        let data : NSData! = NSData(contentsOf: url)
        //判斷data不爲空,這裏是由於swift對類型要求很嚴,若是未空的話,會崩潰
        if data != nil {
            //賦值圖片
            self.image = UIImage.init(data: data as Data, scale: 1)
        }else{
            // 不然就賦值默認圖片
            self.image = UIImage.init(named: "005")
        }
    }

public func kf_setImage ... 這裏的public 暫時先不加 看後面會有什麼影響,若是我本身寫 我就不會加。

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 }
}

思考這裏爲何用協議?

相關文章
相關標籤/搜索