Kingfisher源碼解析系列,因爲水平有限,哪裏有錯,肯請不吝賜教vim
Kingfisher中內置的ImageResource和URL實現了Resource協議,ImageResource和URL的區別是ImageResource可自定義cacheKey。api
let url = URL(string: "https://test/image.jpg")!
imageView.kf.setImage(with: url)
複製代碼
let imageResource = ImageResource(downloadURL: url, cacheKey: "custom_cache_key")
imageView.kf.setImage(with: imageResource)
複製代碼
Kingfisher內置了LocalFileImageDataProvider,Base64ImageDataProvider,RawImageDataProvider三種ImageDataProvider。緩存
let fileUrl = Bundle.main.url(forResource: "image", withExtension: "jpg")!
let imageDataProvider = LocalFileImageDataProvider(fileURL: fileUrl)
imageView.kf.setImage(with: imageDataProvider)
複製代碼
let base64String = "...."
let base64ImageDataProvider = Base64ImageDataProvider(base64String: base64String, cacheKey: "base64_cache_key")
imageView.kf.setImage(with: base64ImageDataProvider)
複製代碼
let data = Data()
let dataImageDataProvider = RawImageDataProvider(data: data, cacheKey: "data_cache_key")
imageView.kf.setImage(with: dataImageDataProvider)
複製代碼
//定義
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)
複製代碼
let placeholderImage = UIImage(named: "placeholder.png")
imageView.kf.setImage(with: url, placeholder: placeholderImage)
複製代碼
// 定義
// 須要使自定義View遵循Placeholder協議
// 能夠什麼都不實現,是由於當Placeholder爲UIview的時候有默認實現
class PlaceholderView: UIView, Placeholder {
}
// 使用
let placeholderView = PlaceholderView()
imageView.kf.setImage(with: url, placeholder: placeholderView)
複製代碼
let url = URL(string: "https://test/image.gif")!
imageView.kf.setImage(with: url)
複製代碼
let url = URL(string: "https://test/image.gif")!
animatedImageView.kf.setImage(with: url)
複製代碼
上面兩者的區別請參考Kingfisher源碼解析之加載動圖 bash
imageView.kf.indicatorType = .none
複製代碼
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)
複製代碼
// 定義
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)
複製代碼
transition用於圖片加載完成以後的展現動畫,有如下類型app
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)?)
}
複製代碼
使用方式dom
imageView.kf.setImage(with: url, options: [.transition(.fade(0.2))])
複製代碼
將下載的數據轉換爲相應的UIImage。支持PNG,JPEG和GIF格式。ide
修改圖片的混合模式(這裏不知道這麼描述對不對),核心實現以下函數
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
複製代碼
在image上添加一層覆蓋,其本質也是混合模式,邏輯大體同上post
給圖片添加高斯模糊,用vimage實現fetch
給圖片添加圓角,支持四個角進行相互組合,使用方式以下
// 設置四個角的圓角
imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20))])
// 給最上角和右下角設置圓角
imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20
,roundingCorners: [.topLeft, .bottomRight]))])
複製代碼
實現方式:利用貝塞爾曲線設置一下帶圓角的圓角矩形,而後對圖片進行裁剪
let path = UIBezierPath(
roundedRect: rect,
byRoundingCorners: corners.uiRectCorner,//此參數表示是哪一個圓角
cornerRadii: CGSize(width: radius, height: radius)
)
context.addPath(path.cgPath)
context.clip()
image.draw(in: rect)
複製代碼
用顏色給圖像主色,實現方式是利用CoreImage中的CIFilter,使用了這2個CIFilter(name: "CIConstantColorGenerator")
和CIFilter(name: "CISourceOverCompositing")
修改圖片的對比度,曝光度,亮度,飽和度,實現方式是利用CoreImage中的CIFilter,使用了這2個CIColorControls
和CIExposureAdjust
使圖像灰度化,是ColorControlsProcessor的特例
對圖片進行裁剪
對圖片下采樣,通常在較小的imageView展現較大的高清圖 核心實現:
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)
}
複製代碼
用於組合多個已有的Processor,使用方式以下,最終都會轉成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,自定義的操做符,調用了append方法
let generalProcessor = BlurImageProcessor(blurRadius: 5) |> RoundCornerImageProcessor(cornerRadius: 20)
複製代碼
參考Kingfisher源碼解析之Processor和CacheSerializer
一般狀況下,會直接經過URL去加載圖片,這個時候cacheKey是URL.absoluteString,也可以使用ImageResource自定義cacheKey
cacheType是一個枚舉,有三個case:.none 未緩存,.memory 存在內存緩存,.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)
複製代碼
cache.retrieveImage(forKey: "cacheKey") { result in
switch result {
case .success(let value):
print(value.cacheType)
print(value.image)
case .failure(let error):
print(error)
}
}
複製代碼
cache.memoryStorage.config.totalCostLimit = 100 * 1024 * 1024
複製代碼
cache.memoryStorage.config.countLimit = 150
複製代碼
cache.memoryStorage.config.expiration = .seconds(300)
複製代碼
也可指定某一個圖片的內存緩存
imageView.kf.setImage(with: url, options:[.memoryCacheExpiration(.never)])
複製代碼
更新策略是一個枚舉,有三個case,.none
過時時間不更新,.cacheTime
在當前時間上加上過時時間,.expirationTime(_ expiration: StorageExpiration)
過時時間更新爲指定多久以後過時。默認值是.cacheTime
,使用方式以下
imageView.kf.setImage(with: url, options:[.memoryCacheAccessExtendingExpiration(.cacheTime)])
複製代碼
cache.memoryStorage.config.cleanInterval = 120
複製代碼
cache.diskStorage.config.sizeLimit = = 500 * 1024 * 1024
複製代碼
同內存緩存
// 普通緩存
let image: UIImage = //...
cache.store(image, forKey: cacheKey)
// 緩存原始數據
let data: Data = //...
let image: UIImage = //...
cache.store(image, original: data, forKey: cacheKey)
複製代碼
forKey: cacheKey,
processorIdentifier: processor.identifier,
fromMemory: false,//是否纔可以內存緩存中刪除
fromDisk: true //是否從磁盤緩存中刪除){}
複製代碼
// 清空內存緩存
cache.clearMemoryCache()
// 清空過時的內存緩存
cache.cleanExpiredMemoryCache()
複製代碼
// 清空磁盤緩存
cache.clearDiskCache()
// 清空過時的磁盤緩存和超過磁盤容量限制的緩存
cache.cleanExpiredDiskCache()
複製代碼
cache.calculateDiskStorageSize()
複製代碼
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)
}
}
複製代碼
// 定義一個requestModifier
let modifier = AnyModifier { request in
var r = request
r.setValue("abc", forHTTPHeaderField: "Access-Token")
return r
}
// 在手動下載時設置
downloader.downloadImage(with: url, options: [.requestModifier(modifier)]) {
}
// 在imageView的setImage的options裏設置
imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])
複製代碼
downloader.downloadTimeout = 60
複製代碼
// 定義一個重定向的處理邏輯
let anyRedirectHandler = AnyRedirectHandler { (task, resp, req, completionHandler) in
completionHandler(req)
}
// 在手動下載時設置
downloader.downloadImage(with: url, options: [.redirectHandler(anyRedirectHandler)])
// 在imageView的setImage的options裏設置
imageView.kf.setImage(with: url, options: [.redirectHandler(anyRedirectHandler)])
複製代碼
// 取消手動下載
let task = downloader.downloadImage(with: url) { result in
}
task?.cancel()
// 取消imageView的下載
let task = imageView.kf.set(with: url)
task?.cancel()
複製代碼
使用方式以下,具體可參考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()
複製代碼