Kingfisher 源码解析(五)

October 15, 2018   

本篇是 Kingfisher 源码分析的最后一篇,这节主要分析一些笔者认为该库比较主要,且值得学习的点。

kf 扩展

首先,Kingfisher 的设计非常巧妙,它并非直接采用给 UIKit 中的组件扩展分类方法的方式,而是通过协议的方式来给需要的类扩展了一个 kf 属性,将需要的方法扩展在这个属性下,私认为是为了在命名上避免与使用者自定义的方法冲突而覆盖的目的

public final class Kingfisher<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

public protocol KingfisherCompatible {
    associatedtype CompatibleType
    var kf: CompatibleType { get }
}

public extension KingfisherCompatible {
    public var kf: Kingfisher<Self> {
        return Kingfisher(self)
    }
}

这样就给需要的类增加了一层包装,在需要满足的类中遵守 KingfisherCompatible 协议,即可给该类扩展出 kf 属性,该属性返回一个该真实类型的实例。

// 给 UIImage 和 NSImage 类扩展 kf 
extension Image: KingfisherCompatible { }
#if !os(watchOS)
// 给 UIImageView 和 NSImageView 扩展 kf
extension ImageView: KingfisherCompatible { }
// 给 UIBUtton 和 NSButton 扩展 kf
extension Button: KingfisherCompatible { }
#else
// WatchOS 中的 图片控件扩展 kf
extension WKInterfaceImage: KingfisherCompatible { }

面向协议编程

swift 利用值类型的 struct 和 协议的设计模式,更鼓励大家面向协议编程。基于 组合优于继承 的理念,swift 底层的基本数据类型全部用协议的方式重新做了设计,同样 Kingfsher 中也大量采用协议的方式来设计接口。包括但不限于

  • URL 的 Resource 协议;

  • 给 ImageView 增加占位图片的 PlaceHolder 协议;

  • 处理图片的 ImageProcessor 协议;

  • 修改网络请求的 ImageDownloadRequestModifier 协议;

  • 图片 Image 和 Data 之间相互转换的协议 CacheSerializer ;

  • 重写调整图片的协议 ImageModifier ;

  • 下载状态的回调代理 ImageDownloaderDelegate ;

  • 给加载状态的控件增加 loading 状态的 Indicator ;

所以面向协议的特性,在这个框架提现地淋漓尽致,如果你想着手开始学习 swift 这门语言的面向协议编程,那么笔者认为这个框架是非常适合入门学习的。

关联值枚举

swift 将枚举的特性可谓发挥到了极致,不仅可以定义枚举,还可以定义属性,设计方法,甚至枚举可以设置关联值 ,这使得枚举更加强大。

框架中的 KingfisherOptionsInfoItem 是一个枚举类型,它包装着一系列下载图片的处理操作,例如:过渡动画、后台解码图片、回调队列、只从缓存中取图等 22 个 可选方式。其中部分就是枚举关联值。它允许你在设置该枚举值时相应的传入一个实例值。

运算符重载

同样在 KingfisherOptionsInfo.swift 文件中,作者为我们展示了如何定义一个全新的运算的过程

// 申明该运算符的优先级
recedencegroup ItemComparisonPrecedence {
    associativity: none
    higherThan: LogicalConjunctionPrecedence
}
// 申明该运算符
infix operator <== : ItemComparisonPrecedence

实现运算符

// 该运算符用来比较两个 `KingfisherOptionsInfoItem` 是否相等
func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Bool {
    switch (lhs, rhs) {
    case (.targetCache(_), .targetCache(_)): return true
    case (.originalCache(_), .originalCache(_)): return true
    case (.downloader(_), .downloader(_)): return true
    case (.transition(_), .transition(_)): return true
    case (.downloadPriority(_), .downloadPriority(_)): return true
    case (.forceRefresh, .forceRefresh): return true
    case (.fromMemoryCacheOrRefresh, .fromMemoryCacheOrRefresh): return true
    case (.forceTransition, .forceTransition): return true
    case (.cacheMemoryOnly, .cacheMemoryOnly): return true
    case (.waitForCache, .waitForCache): return true
    case (.onlyFromCache, .onlyFromCache): return true
    case (.backgroundDecode, .backgroundDecode): return true
    case (.callbackDispatchQueue(_), .callbackDispatchQueue(_)): return true
    case (.scaleFactor(_), .scaleFactor(_)): return true
    case (.preloadAllAnimationData, .preloadAllAnimationData): return true
    case (.requestModifier(_), .requestModifier(_)): return true
    case (.processor(_), .processor(_)): return true
    case (.cacheSerializer(_), .cacheSerializer(_)): return true
    case (.imageModifier(_), .imageModifier(_)): return true
    case (.keepCurrentImageWhileLoading, .keepCurrentImageWhileLoading): return true
    case (.onlyLoadFirstFrame, .onlyLoadFirstFrame): return true
    case (.cacheOriginalImage, .cacheOriginalImage): return true
    default: return false
    }
}

多线程网络

ImageCache 类中,有两个队列: 磁盘读写 ioQueue 串行队列和缓存进度处理 processQueue 并发队列。

当缓存图片和删除时,ioQueue 队列异步执行写入和删除文件的该过程。

当从缓存中获取图片时,从磁盘读取找到后在缓存在内存一份的过程中,就是利用 processQueue 队列并发执行这个缓存过程

// 如果需要在后台解码这张图片时
if options.backgroundDecode {
  sSelf.processQueue.async {

    let result = image.kf.decoded

    sSelf.store(result,
                forKey: key,
                processorIdentifier: options.processor.identifier,
                cacheSerializer: options.cacheSerializer,
                toDisk: false,
                completionHandler: nil)
    options.callbackDispatchQueue.safeAsync {
      completionHandler(imageModifier.modify(result), .disk)
      sSelf = nil
    }
  }
}

大量的条件编译

因为该库同时支持 iOS ,watchOS 和 MacOS 三大平台,所以项目中大量用到了条件编译判断,例如

判断平台版本的 if os(macOS)

系统版本判断 available(iOS 11, tvOS 11.0, macOS 10.13, *)

这么短的篇幅其实很难将整个框架的特点全部罗列完,所以抓一些非常典型的特性进行分析,以上就是笔者对这个框架的一些理解,如果有错误还请斧正。非常感谢~


comments powered by Disqus