KM的博客.

Kingfisher源码

字数统计: 886阅读时长: 4 min
2020/06/30

Kingfisher源码

Kingfisher3架构

kf命名空间

  • Kingfisher 是一个范型类,类型是 Base
  • 协议 KingfisherCompatible,声明属性 kf,类型是范型 CompatibleType 。并要求遵守协议的一方,实现该属性的 get 方法。
  • 协议扩展中,协议自身实现了属性。这样就不必在每个遵守该协议的类里实现该属性了。
  • 协议里的 kf 是一个 Kingfisher 类的实例,调用的方法是 Kingfisher 类的方法。
  • 根据类型的不同,调用不同类型里的方法。如:对应 Image/ImageView / Button 的 Kingfisher 里的setImage 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Kingfisher.swift
public final class Kingfisher<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
/**
A type that has Kingfisher extensions.
*/
public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}

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

extension Image: KingfisherCompatible { }

#if !os(watchOS)
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }
#else
extension WKInterfaceImage: KingfisherCompatible { }
#endif

1、weak var解决CADisplayLink循环引用

为了防止AnimatedImageViewCADisplayLink 之间的循环引用,Kingfisher在AnimatedImageView 内部写了一个代理类。通过TargetProxy 来调用 AnimatedImageView 中的 updateFrame 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
open class AnimatedImageView: UIImageView {

/// Proxy object for prevending a reference cycle between the CADDisplayLink and AnimatedImageView.
class TargetProxy {
private weak var target: AnimatedImageView?

init(target: AnimatedImageView) {
self.target = target
}

@objc func onScreenUpdate() {
target?.updateFrame()
}
}

/// A display link that keeps calling the `updateFrame` method on every screen refresh.
private lazy var displayLink: CADisplayLink = {
self.isDisplayLinkInitialized = true
let displayLink = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate))
displayLink.add(to: .main, forMode: self.runLoopMode)
displayLink.isPaused = true
return displayLink
}()


}

2、处理Gif图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

// MARK: - Image format
private struct ImageHeaderData {
static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
static var JPEG_SOI: [UInt8] = [0xFF, 0xD8]
static var JPEG_IF: [UInt8] = [0xFF]
static var GIF: [UInt8] = [0x47, 0x49, 0x46]
}

public enum ImageFormat {
case unknown, PNG, JPEG, GIF
}
extension DataProxy {
public var imageFormat: ImageFormat {
var buffer = [UInt8](repeating: 0, count: 8)
(base as NSData).getBytes(&buffer, length: 8)
if buffer == ImageHeaderData.PNG {
return .PNG
} else if buffer[0] == ImageHeaderData.JPEG_SOI[0] &&
buffer[1] == ImageHeaderData.JPEG_SOI[1] &&
buffer[2] == ImageHeaderData.JPEG_IF[0]
{
return .JPEG
} else if buffer[0] == ImageHeaderData.GIF[0] &&
buffer[1] == ImageHeaderData.GIF[1] &&
buffer[2] == ImageHeaderData.GIF[2]
{
return .GIF
}

return .unknown
}
}

关联对象objc_setAssociatedObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

private var animatedImageDataKey: Void?
private var imageSourceKey: Void?


// MARK: - Image Properties
extension Kingfisher where Base: Image {
fileprivate(set) var animatedImageData: Data? {
get {
return objc_getAssociatedObject(base, &animatedImageDataKey) as? Data
}
set {
objc_setAssociatedObject(base, &animatedImageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

fileprivate(set) var imageSource: ImageSource? {
get {
return objc_getAssociatedObject(base, &imageSourceKey) as? ImageSource
}
set {
objc_setAssociatedObject(base, &imageSourceKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

}

KingfisherManager原理

  • KingfisherManager 包含了downloader和cache
  • 通过URL检索图片
  • 获取图片retrieveImage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/// Main manager class of Kingfisher. It connects Kingfisher downloader and cache.
/// You can use this class to retrieve an image via a specified URL from web or cache.
public class KingfisherManager {

/// Shared manager used by the extensions across Kingfisher.
public static let shared = KingfisherManager()

/// Cache used by this manager
public var cache: ImageCache

/// Downloader used by this manager
public var downloader: ImageDownloader

fileprivate let processQueue: DispatchQueue

convenience init() {
self.init(downloader: .default, cache: .default)
}

init(downloader: ImageDownloader, cache: ImageCache) {
self.downloader = downloader
self.cache = cache

let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)"
processQueue = DispatchQueue(label: processQueueName, attributes: .concurrent)
}
/**
Get an image with resource.
If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first.
If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`.
These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more.

- parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
- parameter options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
- parameter progressBlock: Called every time downloaded data changed. This could be used as a progress UI.
- parameter completionHandler: Called when the whole retrieving process finished.

- returns: A `RetrieveImageTask` task object. You can use this object to cancel the task.
*/
@discardableResult
public func retrieveImage(with resource: Resource,
options: KingfisherOptionsInfo?,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?) -> RetrieveImageTask
{
let task = RetrieveImageTask()
let options = currentDefaultOptions + (options ?? KingfisherEmptyOptionsInfo)
if options.forceRefresh {
_ = downloadAndCacheImage(
with: resource.downloadURL,
forKey: resource.cacheKey,
retrieveImageTask: task,
progressBlock: progressBlock,
completionHandler: completionHandler,
options: options)
} else {
tryToRetrieveImageFromCache(
forKey: resource.cacheKey,
with: resource.downloadURL,
retrieveImageTask: task,
progressBlock: progressBlock,
completionHandler: completionHandler,
options: options)
}

return task
}

}

CATALOG
  1. 1. Kingfisher源码
    1. 1.0.0.1. Kingfisher3架构
  • 1.1. kf命名空间
  • 1.2. 1、weak var解决CADisplayLink循环引用
  • 1.3. 2、处理Gif图片
    1. 1.3.0.1. 关联对象objc_setAssociatedObject
    2. 1.3.0.2. KingfisherManager原理