Kingfisher源碼解析之使用

Kingfisher源碼解析系列,由于水平有限陶耍,哪里有錯力图,肯請不吝賜教

1. 基本使用

1.1 通過Resource設(shè)置圖片

Kingfisher中內(nèi)置的ImageResource和URL實現(xiàn)了Resource協(xié)議构韵,ImageResource和URL的區(qū)別是ImageResource可自定義cacheKey。

  • URL設(shè)置圖片
let url = URL(string: "https://test/image.jpg")!
imageView.kf.setImage(with: url)
  • ImageResource
let imageResource = ImageResource(downloadURL: url, cacheKey: "custom_cache_key")
imageView.kf.setImage(with: imageResource)
1.2 通過ImageDataProvider設(shè)置圖片

Kingfisher內(nèi)置了LocalFileImageDataProvider绰筛,Base64ImageDataProvider,RawImageDataProvider三種ImageDataProvider描融。

  • LocalFileImageDataProvider
let fileUrl = Bundle.main.url(forResource: "image", withExtension: "jpg")!
let imageDataProvider =  LocalFileImageDataProvider(fileURL: fileUrl)
imageView.kf.setImage(with: imageDataProvider)
  • Base64ImageDataProvider
let base64String = "...."
let base64ImageDataProvider = Base64ImageDataProvider(base64String: base64String, cacheKey: "base64_cache_key")
imageView.kf.setImage(with: base64ImageDataProvider)
  • RawImageDataProvider
let data = Data()
let dataImageDataProvider = RawImageDataProvider(data: data, cacheKey: "data_cache_key")
imageView.kf.setImage(with: dataImageDataProvider)
  • 自定義ImageDataProvider
//定義
public struct FileNameImageDataProvider : ImageDataProvider {
    public let 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: "文件不存在", code: -1, userInfo: ["fileName":fileName])
            handler(.failure(error))
        }
    }
}
//使用
let fileNameImageDataProvider = FileNameImageDataProvider(fileName: "image.jpg")
imageView.kf.setImage(with: fileNameImageDataProvider)
1.3 展示placeholder
  • 使用UIImage設(shè)置placeholder
let placeholderImage = UIImage(named: "placeholder.png")
imageView.kf.setImage(with: url, placeholder: placeholderImage)
  • 通過自定義View設(shè)置placeholder
// 定義
// 需要使自定義View遵循Placeholder協(xié)議
// 可以什么都不實現(xiàn)铝噩,是因為當Placeholder為UIview的時候有默認實現(xiàn)
class PlaceholderView: UIView, Placeholder {
}
// 使用
let placeholderView = PlaceholderView()
imageView.kf.setImage(with: url, placeholder: placeholderView)
1.4 加載GIF圖
  • 通過UIImageView加載GIF圖
let url = URL(string: "https://test/image.gif")!
imageView.kf.setImage(with: url)
  • 通過AnimatedImageView加載GIF圖
let url = URL(string: "https://test/image.gif")!
animatedImageView.kf.setImage(with: url)

上面二者的區(qū)別請參考Kingfisher源碼解析之加載動圖

1.5 設(shè)置指示器
  • 不使用指示器
imageView.kf.indicatorType = .none
  • 使用UIActivityIndicatorView作為指示器
imageView.kf.indicatorType = .activity
  • 使用圖片作為指示器
let path = Bundle.main.path(forResource: "loader", ofType: "gif")!
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
imageView.kf.indicatorType = .image(imageData: data)
  • 使用自定義View作為指示器
// 定義
struct CustomIndicator: Indicator {
    let view: UIView = UIView()
    func startAnimatingView() { view.isHidden = false }
    func stopAnimatingView() { view.isHidden = true }
    init() {
        view.backgroundColor = .red
    }
}
// 使用
let indicator = CustomIndicator()
imageView.kf.indicatorType = .custom(indicator: indicator)
1.6 設(shè)置transition

transition用于圖片加載完成之后的展示動畫,有以下類型

public enum ImageTransition {
    // 無動畫
    case none 
    // 相當于UIView.AnimationOptions.transitionCrossDissolve
    case fade(TimeInterval)
    // 相當于UIView.AnimationOptions.transitionFlipFromLeft
    case flipFromLeft(TimeInterval)
    // 相當于UIView.AnimationOptions.transitionFlipFromRight
    case flipFromRight(TimeInterval)
    // 相當于UIView.AnimationOptions.transitionFlipFromTop
    case flipFromTop(TimeInterval)
    // 相當于UIView.AnimationOptions.transitionFlipFromBottom
    case flipFromBottom(TimeInterval)
    // 自定義動畫
    case custom(duration: TimeInterval,
                 options: UIView.AnimationOptions,
              animations: ((UIImageView, UIImage) -> Void)?,
              completion: ((Bool) -> Void)?)
}

使用方式

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

2. Processor

2.1 DefaultImageProcessor

將下載的數(shù)據(jù)轉(zhuǎn)換為相應(yīng)的UIImage窿克。支持PNG骏庸,JPEG和GIF格式。

2.2 BlendImageProcessor

修改圖片的混合模式(這里不知道這么描述對不對)年叮,核心實現(xiàn)如下

  1. 首先利用DefaultImageProcessor把Data轉(zhuǎn)成image具被,然后去繪制
  2. 獲取上下文
  3. 為上下文填充背景色
  4. 調(diào)用image.draw函數(shù)設(shè)置混合模式
  5. 從上下文中獲取圖片為processedImage
  6. 釋放上下文,并返回processedImage
let image = 處理之前的圖片
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
2.3 OverlayImageProcessor

在image上添加一層覆蓋只损,其本質(zhì)也是混合模式一姿,邏輯大致同上

2.4 BlurImageProcessor

給圖片添加高斯模糊,用vimage實現(xiàn)

2.5 RoundCornerImageProcessor

給圖片添加圓角,支持四個角進行相互組合叮叹,使用方式如下

// 設(shè)置四個角的圓角
imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20))])
// 給最上角和右下角設(shè)置圓角
imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20 
                                            ,roundingCorners: [.topLeft, .bottomRight]))])

實現(xiàn)方式:利用貝塞爾曲線設(shè)置一下帶圓角的圓角矩形艾栋,然后對圖片進行裁剪

let path = UIBezierPath(
                roundedRect: rect,
                byRoundingCorners: corners.uiRectCorner,//此參數(shù)表示是哪個圓角
                cornerRadii: CGSize(width: radius, height: radius)
            )
context.addPath(path.cgPath)
context.clip()
image.draw(in: rect)
2.6 TintImageProcessor

用顏色給圖像主色,實現(xiàn)方式是利用CoreImage中的CIFilter衬横,使用了這2個CIFilter(name: "CIConstantColorGenerator")CIFilter(name: "CISourceOverCompositing")

2.7 ColorControlsProcessor

修改圖片的對比度裹粤,曝光度,亮度蜂林,飽和度遥诉,實現(xiàn)方式是利用CoreImage中的CIFilter,使用了這2個CIColorControlsCIExposureAdjust

2.8 BlackWhiteProcessor

使圖像灰度化噪叙,是ColorControlsProcessor的特例

2.9 CroppingImageProcessor

對圖片進行裁剪

2.10 DownsamplingImageProcessor

對圖片下采樣矮锈,一般在較小的imageView展示較大的高清圖
核心實現(xiàn):

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)
}
2.11 GeneralProcessor

用于組合多個已有的Processor,使用方式如下睁蕾,最終都會轉(zhuǎn)成GeneralProcessor

// 使用方式1
let 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)
   }
   return nil
}
// 使用方式2苞笨,此方法是Processor的擴展
let generalProcessor = BlurImageProcessor(blurRadius: 5).append(RoundCornerImageProcessor(cornerRadius: 20)_
// 使用方式3,自定義的操作符子眶,調(diào)用了append方法
let generalProcessor = BlurImageProcessor(blurRadius: 5) |> RoundCornerImageProcessor(cornerRadius: 20)
2.12 自定義Processor

參考Kingfisher源碼解析之Processor和CacheSerializer

3 緩存

3.1 使用自定義的cacheKey

通常情況下瀑凝,會直接通過URL去加載圖片,這個時候cacheKey是URL.absoluteString臭杰,也可使用ImageResource自定義cacheKey

3.2 通過cacheKey判斷是否緩存粤咪,以及緩存的類型

cacheType是一個枚舉,有三個case:.none 未緩存,.memory 存在內(nèi)存緩存,.disk存在磁盤緩存渴杆。
需要說明的是cacheKey+processor.identifier才是緩存的唯一標識符,只是DefaultImageProcessor的identifier為空字符串寥枝,若是在加載的時候指定了非DefaultImageProcessor的Processor,則在查找的時候需要指定processorIdentifier

let cache = ImageCache.default
let isCached = cache.isCached(forKey: cacheKey)
let cacheType = cache.imageCachedType(forKey: cacheKey)
// 若是指定了Processor磁奖,可使用此方法查找緩存
cache.isCached(forKey: cacheKey, processorIdentifier: processor.identifier)
3.3 通過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)
    }
}
3.4 設(shè)置緩存的配置
3.4.1 設(shè)置內(nèi)存緩存的容量限制(默認值設(shè)置物理內(nèi)存的四分之一)
cache.memoryStorage.config.totalCostLimit = 100 * 1024 * 1024
3.4.2 設(shè)置內(nèi)存緩存的個數(shù)限制
cache.memoryStorage.config.countLimit = 150
3.4.3 設(shè)置內(nèi)存緩存的過期時間(默認值是300秒)
cache.memoryStorage.config.expiration = .seconds(300)

也可指定某一個圖片的內(nèi)存緩存

imageView.kf.setImage(with: url, options:[.memoryCacheExpiration(.never)])
3.4.4 設(shè)置內(nèi)存緩存的過期時間更新策略

更新策略是一個枚舉,有三個case,.none 過期時間不更新,.cacheTime 在當前時間上加上過期時間,.expirationTime(_ expiration: StorageExpiration)過期時間更新為指定多久之后過期比搭。默認值是.cacheTime冠跷,使用方式如下

imageView.kf.setImage(with: url, options:[.memoryCacheAccessExtendingExpiration(.cacheTime)])
3.4.5 設(shè)置內(nèi)存緩存清除過期內(nèi)存的時間間隔(此值是不可變的,只可在初始化時賦值)
cache.memoryStorage.config.cleanInterval = 120
3.4.6 設(shè)置磁盤緩存的容量
cache.diskStorage.config.sizeLimit =  = 500 * 1024 * 1024
3.4.7 設(shè)置磁盤緩存的過期時間和過期時間更新策略

同內(nèi)存緩存

3.5手動的緩存圖片
// 普通緩存
let image: UIImage = //...
cache.store(image, forKey: cacheKey)

// 緩存原始數(shù)據(jù)
let data: Data = //...
let image: UIImage = //...
cache.store(image, original: data, forKey: cacheKey)
3.6 清除緩存
3.6.1 刪除指定的緩存
    forKey: cacheKey,
    processorIdentifier: processor.identifier, 
    fromMemory: false,//是否才能夠內(nèi)存緩存中刪除
    fromDisk: true //是否從磁盤緩存中刪除){}
3.6.2 清空內(nèi)存緩存身诺,清空過期的內(nèi)存緩存
// 清空內(nèi)存緩存
cache.clearMemoryCache()
// 清空過期的內(nèi)存緩存
cache.cleanExpiredMemoryCache()
3.6.3 清空磁盤緩存蔽莱,清空過期的磁盤緩存和超過磁盤容量限制的緩存
// 清空磁盤緩存
cache.clearDiskCache()
// 清空過期的磁盤緩存和超過磁盤容量限制的緩存
cache.cleanExpiredDiskCache()
3.7 獲取磁盤緩存大小
cache.calculateDiskStorageSize()

5. 下載

5.1 手動下載圖片
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)
    }
}
5.2 在發(fā)送請求之前,修改Request
// 定義一個requestModifier
let modifier = AnyModifier { request in
    var r = request
    r.setValue("abc", forHTTPHeaderField: "Access-Token")
    return r
}
// 在手動下載時設(shè)置
downloader.downloadImage(with: url, options: [.requestModifier(modifier)]) { 
}
// 在imageView的setImage的options里設(shè)置
imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])
5.3 設(shè)置超時時間
downloader.downloadTimeout = 60
5.4 處理重定向
// 定義一個重定向的處理邏輯
let anyRedirectHandler = AnyRedirectHandler { (task, resp, req, completionHandler) in
        completionHandler(req)
}
// 在手動下載時設(shè)置
downloader.downloadImage(with: url, options: [.redirectHandler(anyRedirectHandler)])
// 在imageView的setImage的options里設(shè)置
imageView.kf.setImage(with: url,  options: [.redirectHandler(anyRedirectHandler)])
5.5 取消下載
// 取消手動下載
let task = downloader.downloadImage(with: url) { result in
}
task?.cancel()

// 取消imageView的下載
let task = imageView.kf.set(with: url)
task?.cancel()

6. 預(yù)加載

使用方式如下戚长,具體可參考Kingfisher源碼解析之ImagePrefetcher

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

7. 一些有用的options

  • loadDiskFileSynchronously 從磁盤中加載時盗冷,是否同步的去加載
  • onlyFromCache 是否只從緩存中加載
  • cacheMemoryOnly 是否只使用內(nèi)存緩存
  • forceRefresh 是否強制刷新,若值為true同廉,則每次都會重新下載
  • backgroundDecode 是否在子線程去解碼
  • ...其他配置請參考Kingfisher源碼解析之Options解釋
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仪糖,一起剝皮案震驚了整個濱河市柑司,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锅劝,老刑警劉巖攒驰,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異故爵,居然都是意外死亡玻粪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門诬垂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劲室,“玉大人,你說我怎么就攤上這事结窘『苎螅” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵隧枫,是天一觀的道長喉磁。 經(jīng)常有香客問我,道長官脓,這世上最難降的妖魔是什么协怒? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮卑笨,結(jié)果婚禮上孕暇,老公的妹妹穿的比我還像新娘。我一直安慰自己湾趾,他們只是感情好芭商,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布派草。 她就那樣靜靜地躺著搀缠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪近迁。 梳的紋絲不亂的頭發(fā)上艺普,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音鉴竭,去河邊找鬼歧譬。 笑死,一個胖子當著我的面吹牛搏存,可吹牛的內(nèi)容都是我干的瑰步。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼璧眠,長吁一口氣:“原來是場噩夢啊……” “哼缩焦!你這毒婦竟也來了读虏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤袁滥,失蹤者是張志新(化名)和其女友劉穎盖桥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體题翻,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡揩徊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嵌赠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塑荒。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猾普,靈堂內(nèi)的尸體忽然破棺而出袜炕,到底是詐尸還是另有隱情,我是刑警寧澤初家,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布偎窘,位于F島的核電站,受9級特大地震影響溜在,放射性物質(zhì)發(fā)生泄漏陌知。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一掖肋、第九天 我趴在偏房一處隱蔽的房頂上張望仆葡。 院中可真熱鬧,春花似錦志笼、人聲如沸沿盅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腰涧。三九已至,卻和暖如春紊浩,著一層夾襖步出監(jiān)牢的瞬間窖铡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工坊谁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留费彼,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓口芍,卻偏偏與公主長得像箍铲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鬓椭,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345