本篇文章为Kingfisher源码导读,不会详细分析源码中的每一个细节,只是分享我阅读源码时的顺序以及思路,按照文中的代码阅读顺序加上你自己主动阅读源码的一些理解,读懂整个Kingfisher系统内部的工作原理应该不是很难的事情。
Source Source表示一个图片资源的来源。KF中图片拥有两种来源,一个是来源于网络(network),一个是来源于本地(provider)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 enum Source { case network(Resource ) case provider(ImageDataProvider ) public var cacheKey: String { switch self { case .network(let resource): return resource.cacheKey case .provider(let provider): return provider.cacheKey } } public var url: URL ? { switch self { case .network(let resource): return resource.downloadURL case .provider(let provider): return provider.contentURL } } }
Resource与ImageDataProvider是两个协议类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public protocol Resource { var cacheKey: String { get } var downloadURL: URL { get } } extension Resource { public func convertToSource () -> Source { return downloadURL.isFileURL ? .provider(LocalFileImageDataProvider (fileURL: downloadURL, cacheKey: cacheKey)) : .network(self ) } } public protocol ImageDataProvider { var cacheKey: String { get } func data (handler : @escaping (Result <Data , Error >) -> Void ) var contentURL: URL ? { get } }
看完了协议类型,再来看对应的两个实现会更加容易理解一些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 extension URL : Resource { public var cacheKey: String { return absoluteString } public var downloadURL: URL { return self } } public struct LocalFileImageDataProvider : ImageDataProvider { public let fileURL: URL public var cacheKey: String public init (fileURL : URL , cacheKey : String ? = nil ) { self .fileURL = fileURL self .cacheKey = cacheKey ?? fileURL.absoluteString } public func data (handler : (Result <Data , Error >) -> Void ) { handler(Result (catching: { try Data (contentsOf: fileURL) })) } public var contentURL: URL ? { return fileURL } }
入口 为了能够满足跨平台的需求,KF定义了一系列类型别名,并且结合条件编译指令,在不同的平台上为这一系列类型别名绑定不同的类型。是非常值得学习的一个做法。此次源码分析我们以iOS平台为例,在iOS平台中这些类型别名存在以下对应关系
KFCrossPlatformImage -> UIImage
KFCrossPlatformColor -> UIColor
KFCrossPlatformImageView -> UIImageView
KFCrossPlatformView -> UIView
KFCrossPlatformButton -> UIButton
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #if os(macOS)import AppKitpublic typealias KFCrossPlatformImage = NSImage public typealias KFCrossPlatformView = NSView public typealias KFCrossPlatformColor = NSColor public typealias KFCrossPlatformImageView = NSImageView public typealias KFCrossPlatformButton = NSButton #else import UIKitpublic typealias KFCrossPlatformImage = UIImage public typealias KFCrossPlatformColor = UIColor #if ! os(watchOS)public typealias KFCrossPlatformImageView = UIImageView public typealias KFCrossPlatformView = UIView public typealias KFCrossPlatformButton = UIButton #else import WatchKit#endif #endif
接着就是入口的代码
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 public protocol KingfisherCompatible : AnyObject { }extension KFCrossPlatformImage : KingfisherCompatible { }#if ! os(watchOS)extension KFCrossPlatformImageView : KingfisherCompatible { }extension KFCrossPlatformButton : KingfisherCompatible { }#else extension WKInterfaceImage : KingfisherCompatible { }#endif extension KingfisherCompatible { public var kf: KingfisherWrapper <Self > { get { return KingfisherWrapper (self ) } set { } } } public struct KingfisherWrapper <Base > { public let base: Base public init (_ base : Base ) { self .base = base } }
kf计算属性返回的KingfisherWrapper只是一个简单的包装器,本身不存储任何状态,并且在使用过后就会立即销毁,那么它是怎么完成那么复杂的操作(图片的下载,缓存,加载)的呢?其实KingfisherWrapper在工作的过程中是可以拿到一些持久存储的状态信息的,Kingfisher直接将一些状态以关联属性的方式存储在特定的Base中了,这里我们以UIImageView为例
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 private var taskIdentifierKey: Void ?private var indicatorKey: Void ?private var indicatorTypeKey: Void ?private var placeholderKey: Void ?private var imageTaskKey: Void ?extension KingfisherWrapper where Base : KFCrossPlatformImageView { public private(set) var taskIdentifier: Source .Identifier .Value ? { get { let box: Box <Source .Identifier .Value >? = getAssociatedObject(base, & taskIdentifierKey) return box? .value } set { let box = newValue.map { Box ($0 ) } setRetainedAssociatedObject(base, & taskIdentifierKey, box) } } public var indicatorType: IndicatorType { get { return getAssociatedObject(base, & indicatorTypeKey) ?? .none } set { switch newValue { case .none: indicator = nil case .activity: indicator = ActivityIndicator () case .image(let data): indicator = ImageIndicator (imageData: data) case .custom(let anIndicator): indicator = anIndicator } setRetainedAssociatedObject(base, & indicatorTypeKey, newValue) } } public private(set) var indicator: Indicator ? { get { let box: Box <Indicator >? = getAssociatedObject(base, & indicatorKey) return box? .value } set { if let previousIndicator = indicator { previousIndicator.view.removeFromSuperview() } if let newIndicator = newValue { let view = newIndicator.view base.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false view.centerXAnchor.constraint( equalTo: base.centerXAnchor, constant: newIndicator.centerOffset.x).isActive = true view.centerYAnchor.constraint( equalTo: base.centerYAnchor, constant: newIndicator.centerOffset.y).isActive = true switch newIndicator.sizeStrategy(in: base) { case .intrinsicSize: break case .full: view.heightAnchor.constraint(equalTo: base.heightAnchor, constant: 0 ).isActive = true view.widthAnchor.constraint(equalTo: base.widthAnchor, constant: 0 ).isActive = true case .size(let size): view.heightAnchor.constraint(equalToConstant: size.height).isActive = true view.widthAnchor.constraint(equalToConstant: size.width).isActive = true } newIndicator.view.isHidden = true } setRetainedAssociatedObject(base, & indicatorKey, newValue.map(Box .init )) } } private var imageTask: DownloadTask ? { get { return getAssociatedObject(base, & imageTaskKey) } set { setRetainedAssociatedObject(base, & imageTaskKey, newValue)} } public private(set) var placeholder: Placeholder ? { get { return getAssociatedObject(base, & placeholderKey) } set { if let previousPlaceholder = placeholder { previousPlaceholder.remove(from: base) } if let newPlaceholder = newValue { newPlaceholder.add(to: base) } else { base.image = nil } setRetainedAssociatedObject(base, & placeholderKey, newValue) } } }
接着就是KingfisherWrapper中的核心方法setImage
了。这里以UIImageView为例(KFCrossPlatformImageView
即对应UIImageView
)
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 extension KingfisherWrapper where Base : KFCrossPlatformImageView { @discardableResult public func setImage ( with resource : Resource ?, placeholder : Placeholder ? = nil , options : KingfisherOptionsInfo ? = nil , progressBlock : DownloadProgressBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? = nil ) -> DownloadTask ? { return setImage( with: resource? .convertToSource(), placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } @discardableResult public func setImage ( with provider : ImageDataProvider ?, placeholder : Placeholder ? = nil , options : KingfisherOptionsInfo ? = nil , progressBlock : DownloadProgressBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? = nil ) -> DownloadTask ? { return setImage( with: provider.map { .provider($0 ) }, placeholder: placeholder, options: options, progressBlock: progressBlock, completionHandler: completionHandler) } @discardableResult public func setImage ( with source : Source ?, placeholder : Placeholder ? = nil , options : KingfisherOptionsInfo ? = nil , progressBlock : DownloadProgressBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? = nil ) -> DownloadTask ? { var mutatingSelf = self guard let source = source else { mutatingSelf.placeholder = placeholder mutatingSelf.taskIdentifier = nil completionHandler? (.failure(KingfisherError .imageSettingError(reason: .emptySource))) return nil } var options = KingfisherParsedOptionsInfo (KingfisherManager .shared.defaultOptions + (options ?? .empty)) let isEmptyImage = base.image == nil && self .placeholder == nil if ! options.keepCurrentImageWhileLoading || isEmptyImage { mutatingSelf.placeholder = placeholder } let maybeIndicator = indicator maybeIndicator? .startAnimatingView() let issuedIdentifier = Source .Identifier .next() mutatingSelf.taskIdentifier = issuedIdentifier if base.shouldPreloadAllAnimation() { options.preloadAllAnimationData = true } if let block = progressBlock { options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect (block)] } if let provider = ImageProgressiveProvider (options, refresh: { image in self .base.image = image }) { options.onDataReceived = (options.onDataReceived ?? []) + [provider] } options.onDataReceived? .forEach { $0 .onShouldApply = { issuedIdentifier == self .taskIdentifier } } let task = KingfisherManager .shared.retrieveImage( with: source, options: options, downloadTaskUpdated: { mutatingSelf.imageTask = $0 }, completionHandler: { result in CallbackQueue .mainCurrentOrAsync.execute { maybeIndicator? .stopAnimatingView() guard issuedIdentifier == self .taskIdentifier else { let reason: KingfisherError .ImageSettingErrorReason do { let value = try result.get() reason = .notCurrentSourceTask(result: value, error: nil , source: source) } catch { reason = .notCurrentSourceTask(result: nil , error: error, source: source) } let error = KingfisherError .imageSettingError(reason: reason) completionHandler? (.failure(error)) return } mutatingSelf.imageTask = nil mutatingSelf.taskIdentifier = nil switch result { case .success(let value): guard self .needsTransition(options: options, cacheType: value.cacheType) else { mutatingSelf.placeholder = nil self .base.image = value.image completionHandler? (result) return } self .makeTransition(image: value.image, transition: options.transition) { completionHandler? (result) } case .failure: if let image = options.onFailureImage { self .base.image = image } completionHandler? (result) } } } ) mutatingSelf.imageTask = task return task } }
KingfisherManager 上面的setImage
方法中除了对当前所操作的对象(如UIImageView)的状态进行一系列的更新操作之外,最重要的操作就是通过KingfisherManager
的retrieve
方法生成了图片加载任务
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 func retrieveImage ( with source : Source , options : KingfisherParsedOptionsInfo , downloadTaskUpdated : DownloadTaskUpdatedBlock ? = nil , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) -> DownloadTask ? { var retrievingContext = RetrievingContext (options: options, originalSource: source) var retryContext: RetryContext ? func startNewRetrieveTask ( with source : Source , downloadTaskUpdated : DownloadTaskUpdatedBlock ? ) { let newTask = self .retrieveImage(with: source, context: retrievingContext) { result in handler(currentSource: source, result: result) } downloadTaskUpdated? (newTask) } func failCurrentSource (_ source : Source , with error : KingfisherError ) { guard ! error.isTaskCancelled else { completionHandler? (.failure(error)) return } if let nextSource = retrievingContext.popAlternativeSource() { startNewRetrieveTask(with: nextSource, downloadTaskUpdated: downloadTaskUpdated) } else { if retrievingContext.propagationErrors.isEmpty { completionHandler? (.failure(error)) } else { retrievingContext.appendError(error, to: source) let finalError = KingfisherError .imageSettingError( reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors) ) completionHandler? (.failure(finalError)) } } } func handler (currentSource : Source , result : (Result <RetrieveImageResult , KingfisherError >)) -> Void { switch result { case .success: completionHandler? (result) case .failure(let error): if let retryStrategy = options.retryStrategy { let context = retryContext? .increaseRetryCount() ?? RetryContext (source: source, error: error) retryContext = context retryStrategy.retry(context: context) { decision in switch decision { case .retry(let userInfo): retryContext? .userInfo = userInfo startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated) case .stop: failCurrentSource(currentSource, with: error) } } } else { guard ! error.isTaskCancelled else { completionHandler? (.failure(error)) return } if let nextSource = retrievingContext.popAlternativeSource() { retrievingContext.appendError(error, to: currentSource) startNewRetrieveTask(with: nextSource, downloadTaskUpdated: downloadTaskUpdated) } else { if retrievingContext.propagationErrors.isEmpty { completionHandler? (.failure(error)) } else { retrievingContext.appendError(error, to: currentSource) let finalError = KingfisherError .imageSettingError( reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors) ) completionHandler? (.failure(finalError)) } } } } } return retrieveImage( with: source, context: retrievingContext) { result in handler(currentSource: source, result: result) } } private func retrieveImage ( with source : Source , context : RetrievingContext , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) -> DownloadTask ? { let options = context.options if options.forceRefresh { return loadAndCacheImage( source: source, context: context, completionHandler: completionHandler)? .value } else { let loadedFromCache = retrieveImageFromCache( source: source, context: context, completionHandler: completionHandler) if loadedFromCache { return nil } if options.onlyFromCache { let error = KingfisherError .cacheError(reason: .imageNotExisting(key: source.cacheKey)) completionHandler? (.failure(error)) return nil } return loadAndCacheImage( source: source, context: context, completionHandler: completionHandler)? .value } }
图片缓存查找
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 func retrieveImageFromCache ( source : Source , context : RetrievingContext , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) -> Bool { let options = context.options let targetCache = options.targetCache ?? cache let key = source.cacheKey let targetImageCached = targetCache.imageCachedType( forKey: key, processorIdentifier: options.processor.identifier) let validCache = targetImageCached.cached && (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory) if validCache { targetCache.retrieveImage(forKey: key, options: options) { result in guard let completionHandler = completionHandler else { return } options.callbackQueue.execute { result.match( onSuccess: { cacheResult in let value: Result <RetrieveImageResult , KingfisherError > if let image = cacheResult.image { value = result.map { RetrieveImageResult ( image: image, cacheType: $0 .cacheType, source: source, originalSource: context.originalSource ) } } else { value = .failure(KingfisherError .cacheError(reason: .imageNotExisting(key: key))) } completionHandler(value) }, onFailure: { _ in completionHandler(.failure(KingfisherError .cacheError(reason: .imageNotExisting(key: key)))) } ) } } return true } let originalCache = options.originalCache ?? targetCache if originalCache === targetCache && options.processor == DefaultImageProcessor .default { return false } let originalImageCacheType = originalCache.imageCachedType( forKey: key, processorIdentifier: DefaultImageProcessor .default.identifier) let canAcceptDiskCache = ! options.fromMemoryCacheOrRefresh let canUseOriginalImageCache = (canAcceptDiskCache && originalImageCacheType.cached) || (! canAcceptDiskCache && originalImageCacheType == .memory) if canUseOriginalImageCache { var optionsWithoutProcessor = options optionsWithoutProcessor.processor = DefaultImageProcessor .default originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in result.match( onSuccess: { cacheResult in guard let image = cacheResult.image else { assertionFailure ("The image (under key: \(key) should be existing in the original cache." ) return } let processor = options.processor (options.processingQueue ?? self .processingQueue).execute { let item = ImageProcessItem .image(image) guard let processedImage= processor.process(item: item, options: options) else { let error = KingfisherError .processorError( reason: .processingFailed(processor: processor, item: item)) options.callbackQueue.execute { completionHandler? (.failure(error)) } return } var cacheOptions = options cacheOptions.callbackQueue = .untouch let coordinator = CacheCallbackCoordinator ( shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false ) targetCache.store( processedImage, forKey: key, options: cacheOptions, toDisk: ! options.cacheMemoryOnly) { _ in coordinator.apply(.cachingImage) { let value = RetrieveImageResult ( image: processedImage, cacheType: .none, source: source, originalSource: context.originalSource ) options.callbackQueue.execute { completionHandler? (.success(value)) } } } coordinator.apply(.cacheInitiated) { let value = RetrieveImageResult ( image: processedImage, cacheType: .none, source: source, originalSource: context.originalSource ) options.callbackQueue.execute { completionHandler? (.success(value)) } } } }, onFailure: { _ in options.callbackQueue.execute { completionHandler? ( .failure(KingfisherError .cacheError(reason: .imageNotExisting(key: key))) ) } } ) } return true } return false } }
图片加载
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 @discardableResult func loadAndCacheImage ( source : Source , context : RetrievingContext , completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) -> DownloadTask .WrappedTask ? { let options = context.options func _cacheImage (_ result : Result <ImageLoadingResult , KingfisherError >) { cacheImage( source: source, options: options, context: context, result: result, completionHandler: completionHandler ) } switch source { case .network(let resource): let downloader = options.downloader ?? self .downloader let task = downloader.downloadImage( with: resource.downloadURL, options: options, completionHandler: _cacheImage ) if let task = task { return .download(task) } else { return nil } case .provider(let provider): provideImage(provider: provider, options: options, completionHandler: _cacheImage) return .dataProviding } }
缓存图片
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 71 72 73 private func cacheImage ( source : Source , options : KingfisherParsedOptionsInfo , context : RetrievingContext , result : Result <ImageLoadingResult , KingfisherError >, completionHandler : ((Result <RetrieveImageResult , KingfisherError >) -> Void )? ) { switch result { case .success(let value): let needToCacheOriginalImage = options.cacheOriginalImage && options.processor != DefaultImageProcessor .default let coordinator = CacheCallbackCoordinator ( shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage) let targetCache = options.targetCache ?? self .cache targetCache.store( value.image, original: value.originalData, forKey: source.cacheKey, options: options, toDisk: ! options.cacheMemoryOnly) { _ in coordinator.apply(.cachingImage) { let result = RetrieveImageResult ( image: value.image, cacheType: .none, source: source, originalSource: context.originalSource ) completionHandler? (.success(result)) } } if needToCacheOriginalImage { let originalCache = options.originalCache ?? targetCache originalCache.storeToDisk( value.originalData, forKey: source.cacheKey, processorIdentifier: DefaultImageProcessor .default.identifier, expiration: options.diskCacheExpiration) { _ in coordinator.apply(.cachingOriginalImage) { let result = RetrieveImageResult ( image: value.image, cacheType: .none, source: source, originalSource: context.originalSource ) completionHandler? (.success(result)) } } } coordinator.apply(.cacheInitiated) { let result = RetrieveImageResult ( image: value.image, cacheType: .none, source: source, originalSource: context.originalSource ) completionHandler? (.success(result)) } case .failure(let error): completionHandler? (.failure(error)) } }
一些细节:上面的代码中我们经常会用到CacheCallbackCoordinator类,这个类主要负责记录当前缓存的状态,并根据状态之间的改变有条件的触发trigger闭包,其核心代码在下方
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 swift func apply (_ action : Action , trigger : () -> Void ) { switch (state, action) { case (.done, _ ): break case (.idle, .cacheInitiated): if ! shouldWaitForCache { state = .done trigger() } case (.idle, .cachingImage): if shouldCacheOriginal { state = .imageCached } else { state = .done trigger() } case (.idle, .cachingOriginalImage): state = .originalImageCached case (.imageCached, .cachingOriginalImage): state = .done trigger() case (.originalImageCached, .cachingImage): state = .done trigger() default : assertionFailure ("This case should not happen in CacheCallbackCoordinator: \(state) - \(action) " ) } }
缓存 上面的KingfisherManager的代码当中经常会使用到targetCache与originalCache都是ImageCache的一个单例对象,ImageCache类的内部吃用内存存储与硬盘存储的实例,并且会监听系统相关的状态变化并响应变化。收到UIApplication.didReceiveNotification
之后会主动清理内存缓存,收到UIApplication.WillTerminateNotification
之后会主动清理已经过期的缓存,收到UIApplication.didEnterBackgrounNotification
之后会在后台清理过气的缓存
1 2 3 4 5 6 7 8 9 10 11 open class ImageCache { public static let `default` = ImageCache (name: "default" ) public let memoryStorage: MemoryStorage .Backend <KFCrossPlatformImage > public let diskStorage: DiskStorage .Backend <Data > private let ioQueue: DispatchQueue }
在外部,我们通过调用ImageCache的store方法来向缓存中插入图片数据
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 open func store ( _ image : KFCrossPlatformImage , original : Data ? = nil , forKey key : String , options : KingfisherParsedOptionsInfo , toDisk : Bool = true , completionHandler : ((CacheStoreResult ) -> Void )? = nil ){ let identifier = options.processor.identifier let callbackQueue = options.callbackQueue let computedKey = key.computedKey(with: identifier) memoryStorage.storeNoThrow(value: image, forKey: computedKey, expiration: options.memoryCacheExpiration) guard toDisk else { if let completionHandler = completionHandler { let result = CacheStoreResult (memoryCacheResult: .success(()), diskCacheResult: .success(())) callbackQueue.execute { completionHandler(result) } } return } ioQueue.async { let serializer = options.cacheSerializer if let data = serializer.data(with: image, original: original) { self .syncStoreToDisk( data, forKey: key, processorIdentifier: identifier, callbackQueue: callbackQueue, expiration: options.diskCacheExpiration, completionHandler: completionHandler) } else { guard let completionHandler = completionHandler else { return } let diskError = KingfisherError .cacheError( reason: .cannotSerializeImage(image: image, original: original, serializer: serializer)) let result = CacheStoreResult ( memoryCacheResult: .success(()), diskCacheResult: .failure(diskError)) callbackQueue.execute { completionHandler(result) } } } }
存储至内存:MemoryStorage实例的storeNoThrow方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func storeNoThrow ( value : T , forKey key : String , expiration : StorageExpiration ? = nil ) { lock.lock() defer { lock.unlock() } let expiration = expiration ?? config.expiration guard ! expiration.isExpired else { return } let object = StorageObject (value, key: key, expiration: expiration) storage.setObject(object, forKey: key as NSString , cost: value.cacheCost) keys.insert(key) }
存储至硬盘:DiskStorage实例的store方法
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 func store ( value : T , forKey key : String , expiration : StorageExpiration ? = nil ) throws { let expiration = expiration ?? config.expiration guard ! expiration.isExpired else { return } let data: Data do { data = try value.toData() } catch { throw KingfisherError .cacheError(reason: .cannotConvertToData(object: value, error: error)) } let fileURL = cacheFileURL(forKey: key) do { try data.write(to: fileURL) } catch { throw KingfisherError .cacheError( reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error) ) } let now = Date () let attributes: [FileAttributeKey : Any ] = [ .creationDate: now.fileAttributeDate, .modificationDate: expiration.estimatedExpirationSinceNow.fileAttributeDate ] do { try config.fileManager.setAttributes(attributes, ofItemAtPath: fileURL.path) } catch { try? config.fileManager.removeItem(at: fileURL) throw KingfisherError .cacheError( reason: .cannotSetCacheFileAttribute( filePath: fileURL.path, attributes: attributes, error: error ) ) } maybeCachedCheckingQueue.async { self .maybeCached? .insert(fileURL.lastPathComponent) } }
相关通知的监听
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 let notifications = [ (UIApplication .didReceiveMemoryWarningNotification, #selector (clearMemoryCache)), (UIApplication .willTerminateNotification, #selector (cleanExpiredDiskCache)), (UIApplication .didEnterBackgroundNotification, #selector (backgroundCleanExpiredDiskCache)) ] notifications.forEach { NotificationCenter .default.addObserver(self , selector: $0 .1 , name: $0 .0 , object: nil ) } @objc public func clearMemoryCache () { try? memoryStorage.removeAll() } @objc func cleanExpiredDiskCache () { cleanExpiredDiskCache(completion: nil ) } @objc public func backgroundCleanExpiredDiskCache () { guard let sharedApplication = KingfisherWrapper <UIApplication >.shared else { return } func endBackgroundTask (_ task : inout UIBackgroundTaskIdentifier ) { sharedApplication.endBackgroundTask(task) #if swift(>= 4.2 ) task = UIBackgroundTaskIdentifier .invalid #else task = UIBackgroundTaskInvalid #endif } var backgroundTask: UIBackgroundTaskIdentifier ! backgroundTask = sharedApplication.beginBackgroundTask { endBackgroundTask(& backgroundTask! ) } cleanExpiredDiskCache { endBackgroundTask(& backgroundTask! ) } }
缓存移除
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 open func removeImage (forKey key : String , processorIdentifier identifier : String = "" , fromMemory : Bool = true , fromDisk : Bool = true , callbackQueue : CallbackQueue = .untouch, completionHandler : (() -> Void )? = nil ) { let computedKey = key.computedKey(with: identifier) if fromMemory { try? memoryStorage.remove(forKey: computedKey) } if fromDisk { ioQueue.async{ try? self .diskStorage.remove(forKey: computedKey) if let completionHandler = completionHandler { callbackQueue.execute { completionHandler() } } } } else { if let completionHandler = completionHandler { callbackQueue.execute { completionHandler() } } } } func remove (forKey key : String ) throws { lock.lock() defer { lock.unlock() } storage.removeObject(forKey: key as NSString ) keys.remove(key) } func removeFile (at url : URL ) throws { try config.fileManager.removeItem(at: url) }
网络加载 如果缓存中没有查找到对应的图片数据,则会生成一个DownloadTask来下载图片的数据。从下面的代码中可以看出来DownloadTask只是对SessionDataTask的简单封装,核心的图片下载逻辑室友SessionDataTask完成的。
1 2 3 4 5 6 7 8 9 public struct DownloadTask { public let sessionTask: SessionDataTask public let cancelToken: SessionDataTask .CancelToken public func cancel () { sessionTask.cancel(token: cancelToken) } }
SessionDataTask
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 71 72 73 74 75 76 77 78 79 80 81 82 83 public class SessionDataTask { public typealias CancelToken = Int struct TaskCallback { let onCompleted: Delegate <Result <ImageLoadingResult , KingfisherError >, Void >? let options: KingfisherParsedOptionsInfo } public private(set) var mutableData: Data public let task: URLSessionDataTask private var callbacksStore = [CancelToken : TaskCallback ]() var callbacks: [SessionDataTask .TaskCallback ] { lock.lock() defer { lock.unlock() } return Array (callbacksStore.values) } private var currentToken = 0 private let lock = NSLock () let onTaskDone = Delegate <(Result <(Data , URLResponse ?), KingfisherError >, [TaskCallback ]), Void >() let onCallbackCancelled = Delegate <(CancelToken , TaskCallback ), Void >() var started = false var containsCallbacks: Bool { return ! callbacks.isEmpty } init (task : URLSessionDataTask ) { self .task = task mutableData = Data () } func addCallback (_ callback : TaskCallback ) -> CancelToken { lock.lock() defer { lock.unlock() } callbacksStore[currentToken] = callback defer { currentToken += 1 } return currentToken } func removeCallback (_ token : CancelToken ) -> TaskCallback ? { lock.lock() defer { lock.unlock() } if let callback = callbacksStore[token] { callbacksStore[token] = nil return callback } return nil } func resume () { guard ! started else { return } started = true task.resume() } func cancel (token : CancelToken ) { guard let callback = removeCallback(token) else { return } if callbacksStore.count == 0 { task.cancel() } onCallbackCancelled.call((token, callback)) } func forceCancel () { for token in callbacksStore.keys { cancel(token: token) } } func didReceiveData (_ data : Data ) { mutableData.append(data) } }
SessionDataTask是对原生URLSessionDataTask的简单封装,初始化时需要传入一个已经创建好的URLSessionDataTask,而URLSessionDataTask是由URLSession生成的,ImageDownloader就是负责这个的:管理全局的URLSession,并创建URLSessionDataTask,最后再创建出SessionDataTask
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public init (name : String ) { if name.isEmpty { fatalError ("[Kingfisher] You should specify a name for the downloader. " + "A downloader with empty name is not permitted." ) } self .name = name sessionDelegate = SessionDelegate () session = URLSession ( configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil ) authenticationChallengeResponder = self setupSessionHandler() }
ImageDownloader通过downloadImage方法来创建SessionDataTask
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 class Delegate <Input , Output > { init () {} private var block: ((Input ) -> Output ?)? func delegate <T : AnyObject >(on target : T , block : ((T , Input ) -> Output )? ) { self .block = { [weak target] input in guard let target = target else { return nil } return block? (target, input) } } func call (_ input : Input ) -> Output ? { return block? (input) } } open var sessionConfiguration = URLSessionConfiguration .ephemeral { didSet { session.invalidateAndCancel() session = URLSession ( configuration: sessionConfiguration, delegate:sessionDelegate, delegateQueue: nil ) } } @discardableResult open func downloadImage ( with url : URL , options : KingfisherParsedOptionsInfo , completionHandler : ((Result <ImageLoadingResult , KingfisherError >) -> Void )? = nil ) -> DownloadTask ? { var request = URLRequest (url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout) request.httpShouldUsePipelining = requestsUsePipelining if let requestModifier = options.requestModifier { guard let r = requestModifier.modified(for: request) else { options.callbackQueue.execute { completionHandler? (.failure(KingfisherError .requestError(reason: .emptyRequest))) } return nil } request = r } guard let url = request.url, ! url.absoluteString.isEmpty else { options.callbackQueue.execute { completionHandler? (.failure(KingfisherError .requestError(reason: .invalidURL(request: request)))) } return nil } let onCompleted = completionHandler.map { block -> Delegate <Result <ImageLoadingResult , KingfisherError >, Void > in let delegate = Delegate <Result <ImageLoadingResult , KingfisherError >, Void >() delegate.delegate(on: self ) { (_ , callback) in block(callback) } return delegate } let callback = SessionDataTask .TaskCallback ( onCompleted: onCompleted, options: options ) let downloadTask: DownloadTask if let existingTask = sessionDelegate.task(for: url) { downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback) } else { let sessionDataTask = session.dataTask(with: request) sessionDataTask.priority = options.downloadPriority downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback) } if ! sessionTask.started { sessionTask.onTaskDone.delegate(on: self ) { (self , done) in let (result, callbacks) = done do { let value = try result.get() self .delegate? .imageDownloader( self , didFinishDownloadingImageForURL: url, with: value.1 , error: nil ) } catch { self .delegate? .imageDownloader( self , didFinishDownloadingImageForURL: url, with: nil , error: error ) } switch result { case .success(let (data, response)): let processor = ImageDataProcessor ( data: data, callbacks: callbacks processingQueue: options.processingQueue ) processor.onImageProcessed.delegate(on: self ) { (self , result) in let (result, callback) = result if let image = try? result.get() { self .delegate? .imageDownloader(self , didDownload: image, for: url, with: response) } let imageResult = result.map { ImageLoadingResult (image: $0 , url: url, originalData: data) } let queue = callback.options.callbackQueue queue.execute { callback.onCompleted? .call(imageResult) } } processor.process() case .failure(let error): callbacks.forEach { callback in let queue = callback.options.callbackQueue queue.execute { callback.onCompleted? .call(.failure(error)) } } } } delegate? .imageDownloader(self , willDownloadImageForURL: url, with: request) sessionTask.resume() } return downloadTask }
SessionDelegate类作为URLSession的代理,同时也负责管理我们创建的DownloadTask。SessionDelegate中有一个字典属性,该字典以下载任务的URL为key,DownloadTask为value。在上面我们分析的downloadImage用来创建图片下载任务的方法中可以看出来这个字典的作用。每次创建一个DownloadTask就把这个task放入SessionDelegate的字典中存储,等到下次需要使用到同一张图片的时候就不用重复创建下载任务。并且如果这个下载任务还未结束就又遇到了同一个URL的下载任务,甚至可以不用重复请求,直接使用上一次的请求结果。
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 func add ( _ dataTask : URLSessionDataTask , url : URL , callback : SessionDataTask .TaskCallback ) -> DownloadTask { lock.lock() defer { lock.unlock() } let task = SessionDataTask (task: dataTask) task.onCallbackCancelled.delegate(on: self ) { [weak task] (self , value) in guard let task = task else { return } let (token, callback) = value let error = KingfisherError .requestError(reason: .taskCancelled(task: task, token: token)) task.onTaskDone.call((.failure(error), [callback])) if ! task.containsCallbacks { let dataTask = task.task self .remove(dataTask) } } let token = task.addCallback(callback) tasks[url] = task return DownloadTask (sessionTask: task, cancelToken: token) } func append ( _ task : SessionDataTask , url : URL , callback : SessionDataTask .TaskCallback ) -> DownloadTask { let token = task.addCallback(callback) return DownloadTask (sessionTask: task, cancelToken: token) } private func remove (_ task : URLSessionTask ) { guard let url = task.originalRequest? .url else { return } lock.lock() defer {lock.unlock()} tasks[url] = nil }