You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

673 lines
29 KiB

  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 animated 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 animated 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. var info = KingfisherParsedOptionsInfo(options)
  167. if let block = progressBlock {
  168. info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
  169. }
  170. return retrieveImage(
  171. with: source,
  172. options: info,
  173. downloadTaskUpdated: downloadTaskUpdated,
  174. completionHandler: completionHandler)
  175. }
  176. func retrieveImage(
  177. with source: Source,
  178. options: KingfisherParsedOptionsInfo,
  179. downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
  180. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  181. {
  182. var context = RetrievingContext(options: options, originalSource: source)
  183. func handler(currentSource: Source, result: (Result<RetrieveImageResult, KingfisherError>)) -> Void {
  184. switch result {
  185. case .success:
  186. completionHandler?(result)
  187. case .failure(let error):
  188. // Skip alternative sources if the user cancelled it.
  189. guard !error.isTaskCancelled else {
  190. completionHandler?(.failure(error))
  191. return
  192. }
  193. if let nextSource = context.popAlternativeSource() {
  194. context.appendError(error, to: currentSource)
  195. let newTask = self.retrieveImage(with: nextSource, context: context) { result in
  196. handler(currentSource: nextSource, result: result)
  197. }
  198. downloadTaskUpdated?(newTask)
  199. } else {
  200. // No other alternative source. Finish with error.
  201. if context.propagationErrors.isEmpty {
  202. completionHandler?(.failure(error))
  203. } else {
  204. context.appendError(error, to: currentSource)
  205. let finalError = KingfisherError.imageSettingError(
  206. reason: .alternativeSourcesExhausted(context.propagationErrors)
  207. )
  208. completionHandler?(.failure(finalError))
  209. }
  210. }
  211. }
  212. }
  213. return retrieveImage(
  214. with: source,
  215. context: context)
  216. {
  217. result in
  218. handler(currentSource: source, result: result)
  219. }
  220. }
  221. private func retrieveImage(
  222. with source: Source,
  223. context: RetrievingContext,
  224. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  225. {
  226. let options = context.options
  227. if options.forceRefresh {
  228. return loadAndCacheImage(
  229. source: source,
  230. context: context,
  231. completionHandler: completionHandler)?.value
  232. } else {
  233. let loadedFromCache = retrieveImageFromCache(
  234. source: source,
  235. context: context,
  236. completionHandler: completionHandler)
  237. if loadedFromCache {
  238. return nil
  239. }
  240. if options.onlyFromCache {
  241. let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey))
  242. completionHandler?(.failure(error))
  243. return nil
  244. }
  245. return loadAndCacheImage(
  246. source: source,
  247. context: context,
  248. completionHandler: completionHandler)?.value
  249. }
  250. }
  251. func provideImage(
  252. provider: ImageDataProvider,
  253. options: KingfisherParsedOptionsInfo,
  254. completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)?)
  255. {
  256. guard let completionHandler = completionHandler else { return }
  257. provider.data { result in
  258. switch result {
  259. case .success(let data):
  260. (options.processingQueue ?? self.processingQueue).execute {
  261. let processor = options.processor
  262. let processingItem = ImageProcessItem.data(data)
  263. guard let image = processor.process(item: processingItem, options: options) else {
  264. options.callbackQueue.execute {
  265. let error = KingfisherError.processorError(
  266. reason: .processingFailed(processor: processor, item: processingItem))
  267. completionHandler(.failure(error))
  268. }
  269. return
  270. }
  271. options.callbackQueue.execute {
  272. let result = ImageLoadingResult(image: image, url: nil, originalData: data)
  273. completionHandler(.success(result))
  274. }
  275. }
  276. case .failure(let error):
  277. options.callbackQueue.execute {
  278. let error = KingfisherError.imageSettingError(
  279. reason: .dataProviderError(provider: provider, error: error))
  280. completionHandler(.failure(error))
  281. }
  282. }
  283. }
  284. }
  285. private func cacheImage(
  286. source: Source,
  287. options: KingfisherParsedOptionsInfo,
  288. context: RetrievingContext,
  289. result: Result<ImageLoadingResult, KingfisherError>,
  290. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?
  291. )
  292. {
  293. switch result {
  294. case .success(let value):
  295. let needToCacheOriginalImage = options.cacheOriginalImage &&
  296. options.processor != DefaultImageProcessor.default
  297. let coordinator = CacheCallbackCoordinator(
  298. shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage)
  299. // Add image to cache.
  300. let targetCache = options.targetCache ?? self.cache
  301. targetCache.store(
  302. value.image,
  303. original: value.originalData,
  304. forKey: source.cacheKey,
  305. options: options,
  306. toDisk: !options.cacheMemoryOnly)
  307. {
  308. _ in
  309. coordinator.apply(.cachingImage) {
  310. let result = RetrieveImageResult(
  311. image: value.image,
  312. cacheType: .none,
  313. source: source,
  314. originalSource: context.originalSource
  315. )
  316. completionHandler?(.success(result))
  317. }
  318. }
  319. // Add original image to cache if necessary.
  320. if needToCacheOriginalImage {
  321. let originalCache = options.originalCache ?? targetCache
  322. originalCache.storeToDisk(
  323. value.originalData,
  324. forKey: source.cacheKey,
  325. processorIdentifier: DefaultImageProcessor.default.identifier,
  326. expiration: options.diskCacheExpiration)
  327. {
  328. _ in
  329. coordinator.apply(.cachingOriginalImage) {
  330. let result = RetrieveImageResult(
  331. image: value.image,
  332. cacheType: .none,
  333. source: source,
  334. originalSource: context.originalSource
  335. )
  336. completionHandler?(.success(result))
  337. }
  338. }
  339. }
  340. coordinator.apply(.cacheInitiated) {
  341. let result = RetrieveImageResult(
  342. image: value.image,
  343. cacheType: .none,
  344. source: source,
  345. originalSource: context.originalSource
  346. )
  347. completionHandler?(.success(result))
  348. }
  349. case .failure(let error):
  350. completionHandler?(.failure(error))
  351. }
  352. }
  353. @discardableResult
  354. func loadAndCacheImage(
  355. source: Source,
  356. context: RetrievingContext,
  357. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask.WrappedTask?
  358. {
  359. let options = context.options
  360. func _cacheImage(_ result: Result<ImageLoadingResult, KingfisherError>) {
  361. cacheImage(
  362. source: source,
  363. options: options,
  364. context: context,
  365. result: result,
  366. completionHandler: completionHandler
  367. )
  368. }
  369. switch source {
  370. case .network(let resource):
  371. let downloader = options.downloader ?? self.downloader
  372. let task = downloader.downloadImage(
  373. with: resource.downloadURL, options: options, completionHandler: _cacheImage
  374. )
  375. return task.map(DownloadTask.WrappedTask.download)
  376. case .provider(let provider):
  377. provideImage(provider: provider, options: options, completionHandler: _cacheImage)
  378. return .dataProviding
  379. }
  380. }
  381. /// Retrieves image from memory or disk cache.
  382. ///
  383. /// - Parameters:
  384. /// - source: The target source from which to get image.
  385. /// - key: The key to use when caching the image.
  386. /// - url: Image request URL. This is not used when retrieving image from cache. It is just used for
  387. /// `RetrieveImageResult` callback compatibility.
  388. /// - options: Options on how to get the image from image cache.
  389. /// - completionHandler: Called when the image retrieving finishes, either with succeeded
  390. /// `RetrieveImageResult` or an error.
  391. /// - Returns: `true` if the requested image or the original image before being processed is existing in cache.
  392. /// Otherwise, this method returns `false`.
  393. ///
  394. /// - Note:
  395. /// The image retrieving could happen in either memory cache or disk cache. The `.processor` option in
  396. /// `options` will be considered when searching in the cache. If no processed image is found, Kingfisher
  397. /// will try to check whether an original version of that image is existing or not. If there is already an
  398. /// original, Kingfisher retrieves it from cache and processes it. Then, the processed image will be store
  399. /// back to cache for later use.
  400. func retrieveImageFromCache(
  401. source: Source,
  402. context: RetrievingContext,
  403. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> Bool
  404. {
  405. let options = context.options
  406. // 1. Check whether the image was already in target cache. If so, just get it.
  407. let targetCache = options.targetCache ?? cache
  408. let key = source.cacheKey
  409. let targetImageCached = targetCache.imageCachedType(
  410. forKey: key, processorIdentifier: options.processor.identifier)
  411. let validCache = targetImageCached.cached &&
  412. (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory)
  413. if validCache {
  414. targetCache.retrieveImage(forKey: key, options: options) { result in
  415. guard let completionHandler = completionHandler else { return }
  416. options.callbackQueue.execute {
  417. result.match(
  418. onSuccess: { cacheResult in
  419. let value: Result<RetrieveImageResult, KingfisherError>
  420. if let image = cacheResult.image {
  421. value = result.map {
  422. RetrieveImageResult(
  423. image: image,
  424. cacheType: $0.cacheType,
  425. source: source,
  426. originalSource: context.originalSource
  427. )
  428. }
  429. } else {
  430. value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
  431. }
  432. completionHandler(value)
  433. },
  434. onFailure: { _ in
  435. completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
  436. }
  437. )
  438. }
  439. }
  440. return true
  441. }
  442. // 2. Check whether the original image exists. If so, get it, process it, save to storage and return.
  443. let originalCache = options.originalCache ?? targetCache
  444. // No need to store the same file in the same cache again.
  445. if originalCache === targetCache && options.processor == DefaultImageProcessor.default {
  446. return false
  447. }
  448. // Check whether the unprocessed image existing or not.
  449. let originalImageCacheType = originalCache.imageCachedType(
  450. forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier)
  451. let canAcceptDiskCache = !options.fromMemoryCacheOrRefresh
  452. let canUseOriginalImageCache =
  453. (canAcceptDiskCache && originalImageCacheType.cached) ||
  454. (!canAcceptDiskCache && originalImageCacheType == .memory)
  455. if canUseOriginalImageCache {
  456. // Now we are ready to get found the original image from cache. We need the unprocessed image, so remove
  457. // any processor from options first.
  458. var optionsWithoutProcessor = options
  459. optionsWithoutProcessor.processor = DefaultImageProcessor.default
  460. originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in
  461. result.match(
  462. onSuccess: { cacheResult in
  463. guard let image = cacheResult.image else {
  464. assertionFailure("The image (under key: \(key) should be existing in the original cache.")
  465. return
  466. }
  467. let processor = options.processor
  468. (options.processingQueue ?? self.processingQueue).execute {
  469. let item = ImageProcessItem.image(image)
  470. guard let processedImage = processor.process(item: item, options: options) else {
  471. let error = KingfisherError.processorError(
  472. reason: .processingFailed(processor: processor, item: item))
  473. options.callbackQueue.execute { completionHandler?(.failure(error)) }
  474. return
  475. }
  476. var cacheOptions = options
  477. cacheOptions.callbackQueue = .untouch
  478. let coordinator = CacheCallbackCoordinator(
  479. shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false)
  480. targetCache.store(
  481. processedImage,
  482. forKey: key,
  483. options: cacheOptions,
  484. toDisk: !options.cacheMemoryOnly)
  485. {
  486. _ in
  487. coordinator.apply(.cachingImage) {
  488. let value = RetrieveImageResult(
  489. image: processedImage,
  490. cacheType: .none,
  491. source: source,
  492. originalSource: context.originalSource
  493. )
  494. options.callbackQueue.execute { completionHandler?(.success(value)) }
  495. }
  496. }
  497. coordinator.apply(.cacheInitiated) {
  498. let value = RetrieveImageResult(
  499. image: processedImage,
  500. cacheType: .none,
  501. source: source,
  502. originalSource: context.originalSource
  503. )
  504. options.callbackQueue.execute { completionHandler?(.success(value)) }
  505. }
  506. }
  507. },
  508. onFailure: { _ in
  509. // This should not happen actually, since we already confirmed `originalImageCached` is `true`.
  510. // Just in case...
  511. options.callbackQueue.execute {
  512. completionHandler?(
  513. .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
  514. )
  515. }
  516. }
  517. )
  518. }
  519. return true
  520. }
  521. return false
  522. }
  523. }
  524. struct RetrievingContext {
  525. var options: KingfisherParsedOptionsInfo
  526. let originalSource: Source
  527. var propagationErrors: [PropagationError] = []
  528. init(options: KingfisherParsedOptionsInfo, originalSource: Source) {
  529. self.originalSource = originalSource
  530. self.options = options
  531. }
  532. mutating func popAlternativeSource() -> Source? {
  533. guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else {
  534. return nil
  535. }
  536. let nextSource = alternativeSources.removeFirst()
  537. options.alternativeSources = alternativeSources
  538. return nextSource
  539. }
  540. @discardableResult
  541. mutating func appendError(_ error: KingfisherError, to source: Source) -> [PropagationError] {
  542. let item = PropagationError(source: source, error: error)
  543. propagationErrors.append(item)
  544. return propagationErrors
  545. }
  546. }
  547. class CacheCallbackCoordinator {
  548. enum State {
  549. case idle
  550. case imageCached
  551. case originalImageCached
  552. case done
  553. }
  554. enum Action {
  555. case cacheInitiated
  556. case cachingImage
  557. case cachingOriginalImage
  558. }
  559. private let shouldWaitForCache: Bool
  560. private let shouldCacheOriginal: Bool
  561. private (set) var state: State = .idle
  562. init(shouldWaitForCache: Bool, shouldCacheOriginal: Bool) {
  563. self.shouldWaitForCache = shouldWaitForCache
  564. self.shouldCacheOriginal = shouldCacheOriginal
  565. }
  566. func apply(_ action: Action, trigger: () -> Void) {
  567. switch (state, action) {
  568. case (.done, _):
  569. break
  570. // From .idle
  571. case (.idle, .cacheInitiated):
  572. if !shouldWaitForCache {
  573. state = .done
  574. trigger()
  575. }
  576. case (.idle, .cachingImage):
  577. if shouldCacheOriginal {
  578. state = .imageCached
  579. } else {
  580. state = .done
  581. trigger()
  582. }
  583. case (.idle, .cachingOriginalImage):
  584. state = .originalImageCached
  585. // From .imageCached
  586. case (.imageCached, .cachingOriginalImage):
  587. state = .done
  588. trigger()
  589. // From .originalImageCached
  590. case (.originalImageCached, .cachingImage):
  591. state = .done
  592. trigger()
  593. default:
  594. assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)")
  595. }
  596. }
  597. }