Kingfisher源碼解析系列,因爲水平有限,哪裏有錯,肯請不吝賜教數組
let imageView = UIImageView()
imageView.kf.setImage(with: URL(string: "gif_url")!)
複製代碼
let imageView = AnimatedImageView()
imageView.kf.setImage(with: URL(string: "gif_url")!)
複製代碼
看了上面2個顯示GIF的方法,咱們可能下面2個疑問,若是你對下面2個問題很清楚,本篇文章你能夠跳過了緩存
首先來看第一種狀況,在這以前,先來看下Kingfisher
中配置項的這個配置public var processor: ImageProcessor = DefaultImageProcessor.default
,這個配置是提供網絡下載完成或者加載完成本地Data以後,會調用processor
的func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?
方法,把Data轉換成UIImage,而processor的默認值是DefaultImageProcessor
,在DefaultImageProcessor
該方法的實現會調用下面這個方法bash
public static func image(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? {
var image: KFCrossPlatformImage?
switch data.kf.imageFormat {
case .JPEG:
image = KFCrossPlatformImage(data: data, scale: options.scale)
case .PNG:
image = KFCrossPlatformImage(data: data, scale: options.scale)
case .GIF:
image = KingfisherWrapper.animatedImage(data: data, options: options)
case .unknown:
image = KFCrossPlatformImage(data: data, scale: options.scale)
}
return image
}
複製代碼
在這個方法裏會先判斷圖片的類型,判斷的方式是取data的前8個字節,感興趣的話,能夠去源碼裏看下,這裏就不貼了,若是是GIF圖的話KingfisherWrapper.animatedImage
這個方法網絡
public static func animatedImage(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? {
let info: [String: Any] = [
kCGImageSourceShouldCache as String: true,
kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF
]
guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else {
return nil
}
//這裏去掉了Macos下的處理
var image: KFCrossPlatformImage?
if options.preloadAll || options.onlyFirstFrame {
guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else {
return nil
}
if options.onlyFirstFrame {
image = animatedImage.images.first
} else {
let duration = options.duration <= 0.0 ? animatedImage.duration : options.duration
image = .animatedImage(with: animatedImage.images, duration: duration)
}
image?.kf.animatedImageData = data
} else {
image = KFCrossPlatformImage(data: data, scale: options.scale)
var kf = image?.kf
kf?.imageSource = imageSource
kf?.animatedImageData = data
}
return image
}
複製代碼
這個方法時展現GIF的核心邏輯,下面詳細介紹下這個方法 首先把data轉成CGImageSource,而後判斷options.preloadAll || options.onlyFirstFrame
的值,其中onlyFirstFrame默認值爲false,若爲false則只加載第一幀,preloadAll這個值,在咱們使用imageView.kf.setImage
時,則取決於imageView的func shouldPreloadAllAnimation()
函數的返回值,此函數是Kingfisher給UIImageView擴展的方法,在UIImageVIew中一直返回trueapp
@objc extension KFCrossPlatformImageView {
func shouldPreloadAllAnimation() -> Bool { return true }
}
複製代碼
也就是說在默認狀況下,在上面的方法裏會把imageSource
轉換成GIFAnimatedImage
類的實例,而在這個類的實例裏,作了獲取GIF圖的每一幀,並獲取每一幀的時間而後加起來,最後經過UIImage.animatedImage(with: [images], duration: duration)
生成一個動圖的image實例,而後把image賦值給imageView.image
ide
下面把imageSource轉成animatedImage的代碼,忽略了較多的異常狀況函數
let options: [String: Any] = [
kCGImageSourceShouldCache as String: true,
kCGImageSourceTypeIdentifierHint as String:kUTTypeGIF
]
//把data轉換成imageSource
let imageSource = CGImageSourceCreateWithData(data as CFData, options as CFDictionary)!
//獲取GIF的總幀數
let frameCount = CGImageSourceGetCount(imageSource)
var images = [UIImage]()
var gifDuration = 0.0
for i in 0..<frameCount {
//獲取第i幀的圖片,並把圖片添加到數組裏去
let cgImage = CGImageSourceCreateImageAtIndex(imageSource, i, options as CFDictionary)!
images.append( UIImage(cgImage: cgImage, scale: 1, orientation: .up))
//若只有一幀,把動畫時間設置成無限大,不然的話獲取每一幀的時間
if frameCount == 1 {
gifDuration = Double.infinity
}else {
//獲取每一幀的屬性,
let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) as! [String: Any]
//獲取屬性中的GIF信息,以及獲取信息中的時間
let gifInfo = properties[kCGImagePropertyGIFDictionary as String] as! [String: Any]
let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
let duration = unclampedDelayTime ?? delayTime
gifDuration += duration?.doubleValue ?? 0.1
}
}
imageView.image = UIImage.animatedImage(with: images, duration: gifDuration)
複製代碼
接着看第二種狀況,如果從內存緩存中加載的,緩存的就是動圖,因此是直接加載的oop
最後看第三種狀況,如果從磁盤中緩存的,Kingfisher又是如何處理的,在這以前,先來看下Kingfisher
中配置項的這個配置public var cacheSerializer: CacheSerializer = DefaultCacheSerializer.default
,這個配置是提供當從磁盤中讀取完數據以後,把數據反序列化爲UIImage,會調用cacheSerializer
的public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?
方法,把Data反序列化爲UIImage,而cacheSerializer的默認值是DefaultCacheSerializer
,在DefaultCacheSerializer
該方法的實現也會調用public static func image(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage?
這個方法,下面就是跟第一種狀況的邏輯同樣了post
下面來看AnimatedImageView是如何加載GIF圖的,上面說imageView的shouldPreloadAllAnimation
一直返回true,而AnimatedImageView重寫了此函數,並返回false,所以option.preloadAll
等於false,因此會走else裏的邏輯,把data轉成image,利用關聯屬性,給image添加了兩個屬性imageSource:CGImageSource
和animatedImageData:Data
,並對其進行賦值fetch
到如今爲止,咱們仍是沒有看到AnimatedImageView是如何展現GIF圖的。接着往下看 AnimatedImageView重寫了image的didSet,而上面的方法返回後,會對imageView.image進行賦值,正好觸發了image的didSet,在這裏開啓了一個CADisplayLink和Animator。
Animator爲imageView提供動圖的數據,每一幀的圖片以及時間,須要注意的是,它並不會一次加載好全部幀的圖片,默認狀況下,只是先加載前10幀,剩下的等須要的再去加載
CADisplayLink,在每次屏幕刷新的時候,去判斷是否須要展現新的一幀圖片,若須要,則刷新imageView
這裏刷新是調用self.layer.setNeedsDisplay()
,而調用此方法,系統會調用layer.delegate
裏的open func display(_ layer: CALayer)
,而UIView的layer.delegate是本身自己,因此會調用AnimatedImageView重寫的display方法,這是我最開始沒有想明白的地方
override open func display(_ layer: CALayer) {
if let currentFrame = animator?.currentFrameImage {
layer.contents = currentFrame.cgImage
} else {
layer.contents = image?.cgImage
}
}
複製代碼
AnimatedImageView支持一下5點特性,而UIImageView都不支持
repeatCount
:循環次數autoPlayAnimatedImage
:是否自動開始播放framePreloadCount
:預加載的幀數backgroundDecode
:是否在後臺解碼runLoopMode
:GIF播放所在的runLoopMode而且AnimatedImageView因爲不用同時解碼全部幀的圖形數據,因此更節省內存,可是因爲多了一些計算因此會比較浪費CPU