KingfisherManager.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. //
  2. // KingfisherManager.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 15/4/6.
  6. //
  7. // Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import Foundation
  27. /// The downloading progress block type.
  28. /// The parameter value is the `receivedSize` of current response.
  29. /// The second parameter is the total expected data length from response's "Content-Length" header.
  30. /// If the expected length is not available, this block will not be called.
  31. public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> Void)
  32. /// Represents the result of a Kingfisher retrieving image task.
  33. public struct RetrieveImageResult {
  34. /// Gets the image object of this result.
  35. public let image: KFCrossPlatformImage
  36. /// Gets the cache source of the image. It indicates from which layer of cache this image is retrieved.
  37. /// If the image is just downloaded from network, `.none` will be returned.
  38. public let cacheType: CacheType
  39. /// The `Source` which this result is related to. This indicated where the `image` of `self` is referring.
  40. public let source: Source
  41. /// The original `Source` from which the retrieve task begins. It can be different from the `source` property.
  42. /// When an alternative source loading happened, the `source` will be the replacing loading target, while the
  43. /// `originalSource` will be kept as the initial `source` which issued the image loading process.
  44. public let originalSource: Source
  45. }
  46. /// A struct that stores some related information of an `KingfisherError`. It provides some context information for
  47. /// a pure error so you can identify the error easier.
  48. public struct PropagationError {
  49. /// The `Source` to which current `error` is bound.
  50. public let source: Source
  51. /// The actual error happens in framework.
  52. public let error: KingfisherError
  53. }
  54. /// The downloading task updated block type. The parameter `newTask` is the updated new task of image setting process.
  55. /// It is a `nil` if the image loading does not require an image downloading process. If an image downloading is issued,
  56. /// this value will contain the actual `DownloadTask` for you to keep and cancel it later if you need.
  57. public typealias DownloadTaskUpdatedBlock = ((_ newTask: DownloadTask?) -> Void)
  58. /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache,
  59. /// to provide a set of convenience methods to use Kingfisher for tasks.
  60. /// You can use this class to retrieve an image via a specified URL from web or cache.
  61. public class KingfisherManager {
  62. /// Represents a shared manager used across Kingfisher.
  63. /// Use this instance for getting or storing images with Kingfisher.
  64. public static let shared = KingfisherManager()
  65. // Mark: Public Properties
  66. /// The `ImageCache` used by this manager. It is `ImageCache.default` by default.
  67. /// If a cache is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be
  68. /// used instead.
  69. public var cache: ImageCache
  70. /// The `ImageDownloader` used by this manager. It is `ImageDownloader.default` by default.
  71. /// If a downloader is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be
  72. /// used instead.
  73. public var downloader: ImageDownloader
  74. /// Default options used by the manager. This option will be used in
  75. /// Kingfisher manager related methods, as well as all view extension methods.
  76. /// You can also passing other options for each image task by sending an `options` parameter
  77. /// to Kingfisher's APIs. The per image options will overwrite the default ones,
  78. /// if the option exists in both.
  79. public var defaultOptions = KingfisherOptionsInfo.empty
  80. // Use `defaultOptions` to overwrite the `downloader` and `cache`.
  81. private var currentDefaultOptions: KingfisherOptionsInfo {
  82. return [.downloader(downloader), .targetCache(cache)] + defaultOptions
  83. }
  84. private let processingQueue: CallbackQueue
  85. private convenience init() {
  86. self.init(downloader: .default, cache: .default)
  87. }
  88. /// Creates an image setting manager with specified downloader and cache.
  89. ///
  90. /// - Parameters:
  91. /// - downloader: The image downloader used to download images.
  92. /// - cache: The image cache which stores memory and disk images.
  93. public init(downloader: ImageDownloader, cache: ImageCache) {
  94. self.downloader = downloader
  95. self.cache = cache
  96. let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)"
  97. processingQueue = .dispatch(DispatchQueue(label: processQueueName))
  98. }
  99. // MARK: - Getting Images
  100. /// Gets an image from a given resource.
  101. /// - Parameters:
  102. /// - resource: The `Resource` object defines data information like key or URL.
  103. /// - options: Options to use when creating the image.
  104. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an
  105. /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in
  106. /// main queue.
  107. /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This
  108. /// usually happens when an alternative source is used to replace the original (failed)
  109. /// task. You can update your reference of `DownloadTask` if you want to manually `cancel`
  110. /// the new task.
  111. /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked
  112. /// from the `options.callbackQueue`. If not specified, the main queue will be used.
  113. /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource,
  114. /// the started `DownloadTask` is returned. Otherwise, `nil` is returned.
  115. ///
  116. /// - Note:
  117. /// This method will first check whether the requested `resource` is already in cache or not. If cached,
  118. /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it
  119. /// will download the `resource`, store it in cache, then call `completionHandler`.
  120. @discardableResult
  121. public func retrieveImage(
  122. with resource: Resource,
  123. options: KingfisherOptionsInfo? = nil,
  124. progressBlock: DownloadProgressBlock? = nil,
  125. downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
  126. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  127. {
  128. return retrieveImage(
  129. with: resource.convertToSource(),
  130. options: options,
  131. progressBlock: progressBlock,
  132. downloadTaskUpdated: downloadTaskUpdated,
  133. completionHandler: completionHandler
  134. )
  135. }
  136. /// Gets an image from a given resource.
  137. ///
  138. /// - Parameters:
  139. /// - source: The `Source` object defines data information from network or a data provider.
  140. /// - options: Options to use when creating the image.
  141. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an
  142. /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in
  143. /// main queue.
  144. /// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This
  145. /// usually happens when an alternative source is used to replace the original (failed)
  146. /// task. You can update your reference of `DownloadTask` if you want to manually `cancel`
  147. /// the new task.
  148. /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked
  149. /// from the `options.callbackQueue`. If not specified, the main queue will be used.
  150. /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource,
  151. /// the started `DownloadTask` is returned. Otherwise, `nil` is returned.
  152. ///
  153. /// - Note:
  154. /// This method will first check whether the requested `source` is already in cache or not. If cached,
  155. /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it
  156. /// will try to load the `source`, store it in cache, then call `completionHandler`.
  157. ///
  158. public func retrieveImage(
  159. with source: Source,
  160. options: KingfisherOptionsInfo? = nil,
  161. progressBlock: DownloadProgressBlock? = nil,
  162. downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
  163. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  164. {
  165. let options = currentDefaultOptions + (options ?? .empty)
  166. let info = KingfisherParsedOptionsInfo(options)
  167. return retrieveImage(
  168. with: source,
  169. options: info,
  170. progressBlock: progressBlock,
  171. downloadTaskUpdated: downloadTaskUpdated,
  172. completionHandler: completionHandler)
  173. }
  174. func retrieveImage(
  175. with source: Source,
  176. options: KingfisherParsedOptionsInfo,
  177. progressBlock: DownloadProgressBlock? = nil,
  178. downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
  179. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  180. {
  181. var info = options
  182. if let block = progressBlock {
  183. info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
  184. }
  185. return retrieveImage(
  186. with: source,
  187. options: info,
  188. downloadTaskUpdated: downloadTaskUpdated,
  189. completionHandler: completionHandler)
  190. }
  191. func retrieveImage(
  192. with source: Source,
  193. options: KingfisherParsedOptionsInfo,
  194. downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
  195. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  196. {
  197. let retrievingContext = RetrievingContext(options: options, originalSource: source)
  198. var retryContext: RetryContext?
  199. func startNewRetrieveTask(
  200. with source: Source,
  201. downloadTaskUpdated: DownloadTaskUpdatedBlock?
  202. ) {
  203. let newTask = self.retrieveImage(with: source, context: retrievingContext) { result in
  204. handler(currentSource: source, result: result)
  205. }
  206. downloadTaskUpdated?(newTask)
  207. }
  208. func failCurrentSource(_ source: Source, with error: KingfisherError) {
  209. // Skip alternative sources if the user cancelled it.
  210. guard !error.isTaskCancelled else {
  211. completionHandler?(.failure(error))
  212. return
  213. }
  214. // When low data mode constrained error, retry with the low data mode source instead of use alternative on fly.
  215. guard !error.isLowDataModeConstrained else {
  216. if let source = retrievingContext.options.lowDataModeSource {
  217. retrievingContext.options.lowDataModeSource = nil
  218. startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated)
  219. } else {
  220. // This should not happen.
  221. completionHandler?(.failure(error))
  222. }
  223. return
  224. }
  225. if let nextSource = retrievingContext.popAlternativeSource() {
  226. retrievingContext.appendError(error, to: source)
  227. startNewRetrieveTask(with: nextSource, downloadTaskUpdated: downloadTaskUpdated)
  228. } else {
  229. // No other alternative source. Finish with error.
  230. if retrievingContext.propagationErrors.isEmpty {
  231. completionHandler?(.failure(error))
  232. } else {
  233. retrievingContext.appendError(error, to: source)
  234. let finalError = KingfisherError.imageSettingError(
  235. reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors)
  236. )
  237. completionHandler?(.failure(finalError))
  238. }
  239. }
  240. }
  241. func handler(currentSource: Source, result: (Result<RetrieveImageResult, KingfisherError>)) -> Void {
  242. switch result {
  243. case .success:
  244. completionHandler?(result)
  245. case .failure(let error):
  246. if let retryStrategy = options.retryStrategy {
  247. let context = retryContext?.increaseRetryCount() ?? RetryContext(source: source, error: error)
  248. retryContext = context
  249. retryStrategy.retry(context: context) { decision in
  250. switch decision {
  251. case .retry(let userInfo):
  252. retryContext?.userInfo = userInfo
  253. startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated)
  254. case .stop:
  255. failCurrentSource(currentSource, with: error)
  256. }
  257. }
  258. } else {
  259. failCurrentSource(currentSource, with: error)
  260. }
  261. }
  262. }
  263. return retrieveImage(
  264. with: source,
  265. context: retrievingContext)
  266. {
  267. result in
  268. handler(currentSource: source, result: result)
  269. }
  270. }
  271. private func retrieveImage(
  272. with source: Source,
  273. context: RetrievingContext,
  274. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  275. {
  276. let options = context.options
  277. if options.forceRefresh {
  278. return loadAndCacheImage(
  279. source: source,
  280. context: context,
  281. completionHandler: completionHandler)?.value
  282. } else {
  283. let loadedFromCache = retrieveImageFromCache(
  284. source: source,
  285. context: context,
  286. completionHandler: completionHandler)
  287. if loadedFromCache {
  288. return nil
  289. }
  290. if options.onlyFromCache {
  291. let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey))
  292. completionHandler?(.failure(error))
  293. return nil
  294. }
  295. return loadAndCacheImage(
  296. source: source,
  297. context: context,
  298. completionHandler: completionHandler)?.value
  299. }
  300. }
  301. func provideImage(
  302. provider: ImageDataProvider,
  303. options: KingfisherParsedOptionsInfo,
  304. completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)?)
  305. {
  306. guard let completionHandler = completionHandler else { return }
  307. provider.data { result in
  308. switch result {
  309. case .success(let data):
  310. (options.processingQueue ?? self.processingQueue).execute {
  311. let processor = options.processor
  312. let processingItem = ImageProcessItem.data(data)
  313. guard let image = processor.process(item: processingItem, options: options) else {
  314. options.callbackQueue.execute {
  315. let error = KingfisherError.processorError(
  316. reason: .processingFailed(processor: processor, item: processingItem))
  317. completionHandler(.failure(error))
  318. }
  319. return
  320. }
  321. let finalImage = options.imageModifier?.modify(image) ?? image
  322. options.callbackQueue.execute {
  323. let result = ImageLoadingResult(image: finalImage, url: nil, originalData: data)
  324. completionHandler(.success(result))
  325. }
  326. }
  327. case .failure(let error):
  328. options.callbackQueue.execute {
  329. let error = KingfisherError.imageSettingError(
  330. reason: .dataProviderError(provider: provider, error: error))
  331. completionHandler(.failure(error))
  332. }
  333. }
  334. }
  335. }
  336. private func cacheImage(
  337. source: Source,
  338. options: KingfisherParsedOptionsInfo,
  339. context: RetrievingContext,
  340. result: Result<ImageLoadingResult, KingfisherError>,
  341. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?
  342. )
  343. {
  344. switch result {
  345. case .success(let value):
  346. let needToCacheOriginalImage = options.cacheOriginalImage &&
  347. options.processor != DefaultImageProcessor.default
  348. let coordinator = CacheCallbackCoordinator(
  349. shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage)
  350. // Add image to cache.
  351. let targetCache = options.targetCache ?? self.cache
  352. targetCache.store(
  353. value.image,
  354. original: value.originalData,
  355. forKey: source.cacheKey,
  356. options: options,
  357. toDisk: !options.cacheMemoryOnly)
  358. {
  359. _ in
  360. coordinator.apply(.cachingImage) {
  361. let result = RetrieveImageResult(
  362. image: value.image,
  363. cacheType: .none,
  364. source: source,
  365. originalSource: context.originalSource
  366. )
  367. completionHandler?(.success(result))
  368. }
  369. }
  370. // Add original image to cache if necessary.
  371. if needToCacheOriginalImage {
  372. let originalCache = options.originalCache ?? targetCache
  373. originalCache.storeToDisk(
  374. value.originalData,
  375. forKey: source.cacheKey,
  376. processorIdentifier: DefaultImageProcessor.default.identifier,
  377. expiration: options.diskCacheExpiration)
  378. {
  379. _ in
  380. coordinator.apply(.cachingOriginalImage) {
  381. let result = RetrieveImageResult(
  382. image: value.image,
  383. cacheType: .none,
  384. source: source,
  385. originalSource: context.originalSource
  386. )
  387. completionHandler?(.success(result))
  388. }
  389. }
  390. }
  391. coordinator.apply(.cacheInitiated) {
  392. let result = RetrieveImageResult(
  393. image: value.image,
  394. cacheType: .none,
  395. source: source,
  396. originalSource: context.originalSource
  397. )
  398. completionHandler?(.success(result))
  399. }
  400. case .failure(let error):
  401. completionHandler?(.failure(error))
  402. }
  403. }
  404. @discardableResult
  405. func loadAndCacheImage(
  406. source: Source,
  407. context: RetrievingContext,
  408. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask.WrappedTask?
  409. {
  410. let options = context.options
  411. func _cacheImage(_ result: Result<ImageLoadingResult, KingfisherError>) {
  412. cacheImage(
  413. source: source,
  414. options: options,
  415. context: context,
  416. result: result,
  417. completionHandler: completionHandler
  418. )
  419. }
  420. switch source {
  421. case .network(let resource):
  422. let downloader = options.downloader ?? self.downloader
  423. let task = downloader.downloadImage(
  424. with: resource.downloadURL, options: options, completionHandler: _cacheImage
  425. )
  426. // The code below is neat, but it fails the Swift 5.2 compiler with a runtime crash when
  427. // `BUILD_LIBRARY_FOR_DISTRIBUTION` is turned on. I believe it is a bug in the compiler.
  428. // Let's fallback to a traditional style before it can be fixed in Swift.
  429. //
  430. // https://github.com/onevcat/Kingfisher/issues/1436
  431. //
  432. // return task.map(DownloadTask.WrappedTask.download)
  433. if let task = task {
  434. return .download(task)
  435. } else {
  436. return nil
  437. }
  438. case .provider(let provider):
  439. provideImage(provider: provider, options: options, completionHandler: _cacheImage)
  440. return .dataProviding
  441. }
  442. }
  443. /// Retrieves image from memory or disk cache.
  444. ///
  445. /// - Parameters:
  446. /// - source: The target source from which to get image.
  447. /// - key: The key to use when caching the image.
  448. /// - url: Image request URL. This is not used when retrieving image from cache. It is just used for
  449. /// `RetrieveImageResult` callback compatibility.
  450. /// - options: Options on how to get the image from image cache.
  451. /// - completionHandler: Called when the image retrieving finishes, either with succeeded
  452. /// `RetrieveImageResult` or an error.
  453. /// - Returns: `true` if the requested image or the original image before being processed is existing in cache.
  454. /// Otherwise, this method returns `false`.
  455. ///
  456. /// - Note:
  457. /// The image retrieving could happen in either memory cache or disk cache. The `.processor` option in
  458. /// `options` will be considered when searching in the cache. If no processed image is found, Kingfisher
  459. /// will try to check whether an original version of that image is existing or not. If there is already an
  460. /// original, Kingfisher retrieves it from cache and processes it. Then, the processed image will be store
  461. /// back to cache for later use.
  462. func retrieveImageFromCache(
  463. source: Source,
  464. context: RetrievingContext,
  465. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> Bool
  466. {
  467. let options = context.options
  468. // 1. Check whether the image was already in target cache. If so, just get it.
  469. let targetCache = options.targetCache ?? cache
  470. let key = source.cacheKey
  471. let targetImageCached = targetCache.imageCachedType(
  472. forKey: key, processorIdentifier: options.processor.identifier)
  473. let validCache = targetImageCached.cached &&
  474. (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory)
  475. if validCache {
  476. targetCache.retrieveImage(forKey: key, options: options) { result in
  477. guard let completionHandler = completionHandler else { return }
  478. options.callbackQueue.execute {
  479. result.match(
  480. onSuccess: { cacheResult in
  481. let value: Result<RetrieveImageResult, KingfisherError>
  482. if let image = cacheResult.image {
  483. value = result.map {
  484. RetrieveImageResult(
  485. image: image,
  486. cacheType: $0.cacheType,
  487. source: source,
  488. originalSource: context.originalSource
  489. )
  490. }
  491. } else {
  492. value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
  493. }
  494. completionHandler(value)
  495. },
  496. onFailure: { _ in
  497. completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
  498. }
  499. )
  500. }
  501. }
  502. return true
  503. }
  504. // 2. Check whether the original image exists. If so, get it, process it, save to storage and return.
  505. let originalCache = options.originalCache ?? targetCache
  506. // No need to store the same file in the same cache again.
  507. if originalCache === targetCache && options.processor == DefaultImageProcessor.default {
  508. return false
  509. }
  510. // Check whether the unprocessed image existing or not.
  511. let originalImageCacheType = originalCache.imageCachedType(
  512. forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier)
  513. let canAcceptDiskCache = !options.fromMemoryCacheOrRefresh
  514. let canUseOriginalImageCache =
  515. (canAcceptDiskCache && originalImageCacheType.cached) ||
  516. (!canAcceptDiskCache && originalImageCacheType == .memory)
  517. if canUseOriginalImageCache {
  518. // Now we are ready to get found the original image from cache. We need the unprocessed image, so remove
  519. // any processor from options first.
  520. var optionsWithoutProcessor = options
  521. optionsWithoutProcessor.processor = DefaultImageProcessor.default
  522. originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in
  523. result.match(
  524. onSuccess: { cacheResult in
  525. guard let image = cacheResult.image else {
  526. assertionFailure("The image (under key: \(key) should be existing in the original cache.")
  527. return
  528. }
  529. let processor = options.processor
  530. (options.processingQueue ?? self.processingQueue).execute {
  531. let item = ImageProcessItem.image(image)
  532. guard let processedImage = processor.process(item: item, options: options) else {
  533. let error = KingfisherError.processorError(
  534. reason: .processingFailed(processor: processor, item: item))
  535. options.callbackQueue.execute { completionHandler?(.failure(error)) }
  536. return
  537. }
  538. var cacheOptions = options
  539. cacheOptions.callbackQueue = .untouch
  540. let coordinator = CacheCallbackCoordinator(
  541. shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false)
  542. targetCache.store(
  543. processedImage,
  544. forKey: key,
  545. options: cacheOptions,
  546. toDisk: !options.cacheMemoryOnly)
  547. {
  548. _ in
  549. coordinator.apply(.cachingImage) {
  550. let value = RetrieveImageResult(
  551. image: processedImage,
  552. cacheType: .none,
  553. source: source,
  554. originalSource: context.originalSource
  555. )
  556. options.callbackQueue.execute { completionHandler?(.success(value)) }
  557. }
  558. }
  559. coordinator.apply(.cacheInitiated) {
  560. let value = RetrieveImageResult(
  561. image: processedImage,
  562. cacheType: .none,
  563. source: source,
  564. originalSource: context.originalSource
  565. )
  566. options.callbackQueue.execute { completionHandler?(.success(value)) }
  567. }
  568. }
  569. },
  570. onFailure: { _ in
  571. // This should not happen actually, since we already confirmed `originalImageCached` is `true`.
  572. // Just in case...
  573. options.callbackQueue.execute {
  574. completionHandler?(
  575. .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
  576. )
  577. }
  578. }
  579. )
  580. }
  581. return true
  582. }
  583. return false
  584. }
  585. }
  586. class RetrievingContext {
  587. var options: KingfisherParsedOptionsInfo
  588. let originalSource: Source
  589. var propagationErrors: [PropagationError] = []
  590. init(options: KingfisherParsedOptionsInfo, originalSource: Source) {
  591. self.originalSource = originalSource
  592. self.options = options
  593. }
  594. func popAlternativeSource() -> Source? {
  595. guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else {
  596. return nil
  597. }
  598. let nextSource = alternativeSources.removeFirst()
  599. options.alternativeSources = alternativeSources
  600. return nextSource
  601. }
  602. @discardableResult
  603. func appendError(_ error: KingfisherError, to source: Source) -> [PropagationError] {
  604. let item = PropagationError(source: source, error: error)
  605. propagationErrors.append(item)
  606. return propagationErrors
  607. }
  608. }
  609. class CacheCallbackCoordinator {
  610. enum State {
  611. case idle
  612. case imageCached
  613. case originalImageCached
  614. case done
  615. }
  616. enum Action {
  617. case cacheInitiated
  618. case cachingImage
  619. case cachingOriginalImage
  620. }
  621. private let shouldWaitForCache: Bool
  622. private let shouldCacheOriginal: Bool
  623. private let stateQueue: DispatchQueue
  624. private var threadSafeState: State = .idle
  625. private (set) var state: State {
  626. set { stateQueue.sync { threadSafeState = newValue } }
  627. get { stateQueue.sync { threadSafeState } }
  628. }
  629. init(shouldWaitForCache: Bool, shouldCacheOriginal: Bool) {
  630. self.shouldWaitForCache = shouldWaitForCache
  631. self.shouldCacheOriginal = shouldCacheOriginal
  632. let stateQueueName = "com.onevcat.Kingfisher.CacheCallbackCoordinator.stateQueue.\(UUID().uuidString)"
  633. self.stateQueue = DispatchQueue(label: stateQueueName)
  634. }
  635. func apply(_ action: Action, trigger: () -> Void) {
  636. switch (state, action) {
  637. case (.done, _):
  638. break
  639. // From .idle
  640. case (.idle, .cacheInitiated):
  641. if !shouldWaitForCache {
  642. state = .done
  643. trigger()
  644. }
  645. case (.idle, .cachingImage):
  646. if shouldCacheOriginal {
  647. state = .imageCached
  648. } else {
  649. state = .done
  650. trigger()
  651. }
  652. case (.idle, .cachingOriginalImage):
  653. state = .originalImageCached
  654. // From .imageCached
  655. case (.imageCached, .cachingOriginalImage):
  656. state = .done
  657. trigger()
  658. // From .originalImageCached
  659. case (.originalImageCached, .cachingImage):
  660. state = .done
  661. trigger()
  662. default:
  663. assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)")
  664. }
  665. }
  666. }