KingfisherManager.swift 35 KB

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