KingfisherManager.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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: Image
  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` from which the retrieve task begins.
  40. public let source: Source
  41. }
  42. /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache,
  43. /// to provide a set of convenience methods to use Kingfisher for tasks.
  44. /// You can use this class to retrieve an image via a specified URL from web or cache.
  45. public class KingfisherManager {
  46. /// Represents a shared manager used across Kingfisher.
  47. /// Use this instance for getting or storing images with Kingfisher.
  48. public static let shared = KingfisherManager()
  49. // Mark: Public Properties
  50. /// The `ImageCache` used by this manager. It is `ImageCache.default` by default.
  51. /// If a cache is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be
  52. /// used instead.
  53. public var cache: ImageCache
  54. /// The `ImageDownloader` used by this manager. It is `ImageDownloader.default` by default.
  55. /// If a downloader is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be
  56. /// used instead.
  57. public var downloader: ImageDownloader
  58. /// Default options used by the manager. This option will be used in
  59. /// Kingfisher manager related methods, as well as all view extension methods.
  60. /// You can also passing other options for each image task by sending an `options` parameter
  61. /// to Kingfisher's APIs. The per image options will overwrite the default ones,
  62. /// if the option exists in both.
  63. public var defaultOptions = KingfisherOptionsInfo.empty
  64. // Use `defaultOptions` to overwrite the `downloader` and `cache`.
  65. private var currentDefaultOptions: KingfisherOptionsInfo {
  66. return [.downloader(downloader), .targetCache(cache)] + defaultOptions
  67. }
  68. private let processingQueue: CallbackQueue
  69. private convenience init() {
  70. self.init(downloader: .default, cache: .default)
  71. }
  72. init(downloader: ImageDownloader, cache: ImageCache) {
  73. self.downloader = downloader
  74. self.cache = cache
  75. let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)"
  76. processingQueue = .dispatch(DispatchQueue(label: processQueueName))
  77. }
  78. // Mark: Getting Images
  79. /// Gets an image from a given resource.
  80. ///
  81. /// - Parameters:
  82. /// - resource: The `Resource` object defines data information like key or URL.
  83. /// - options: Options to use when creating the animated image.
  84. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an
  85. /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in
  86. /// main queue.
  87. /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked
  88. /// from the `options.callbackQueue`. If not specified, the main queue will be used.
  89. /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource,
  90. /// the started `DownloadTask` is returned. Otherwise, `nil` is returned.
  91. ///
  92. /// - Note:
  93. /// This method will first check whether the requested `resource` is already in cache or not. If cached,
  94. /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it
  95. /// will download the `resource`, store it in cache, then call `completionHandler`.
  96. ///
  97. @discardableResult
  98. public func retrieveImage(
  99. with resource: Resource,
  100. options: KingfisherOptionsInfo? = nil,
  101. progressBlock: DownloadProgressBlock? = nil,
  102. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  103. {
  104. let source = Source.network(resource)
  105. return retrieveImage(
  106. with: source, options: options, progressBlock: progressBlock, completionHandler: completionHandler
  107. )
  108. }
  109. /// Gets an image from a given resource.
  110. ///
  111. /// - Parameters:
  112. /// - source: The `Source` object defines data information from network or a data provider.
  113. /// - options: Options to use when creating the animated image.
  114. /// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an
  115. /// `expectedContentLength`, this block will not be called. `progressBlock` is always called in
  116. /// main queue.
  117. /// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked
  118. /// from the `options.callbackQueue`. If not specified, the main queue will be used.
  119. /// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource,
  120. /// the started `DownloadTask` is returned. Otherwise, `nil` is returned.
  121. ///
  122. /// - Note:
  123. /// This method will first check whether the requested `source` is already in cache or not. If cached,
  124. /// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it
  125. /// will try to load the `source`, store it in cache, then call `completionHandler`.
  126. ///
  127. public func retrieveImage(
  128. with source: Source,
  129. options: KingfisherOptionsInfo? = nil,
  130. progressBlock: DownloadProgressBlock? = nil,
  131. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  132. {
  133. let options = currentDefaultOptions + (options ?? .empty)
  134. var info = KingfisherParsedOptionsInfo(options)
  135. if let block = progressBlock {
  136. info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
  137. }
  138. return retrieveImage(
  139. with: source,
  140. options: info,
  141. completionHandler: completionHandler)
  142. }
  143. func retrieveImage(
  144. with source: Source,
  145. options: KingfisherParsedOptionsInfo,
  146. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
  147. {
  148. if options.forceRefresh {
  149. return loadAndCacheImage(
  150. source: source,
  151. options: options,
  152. completionHandler: completionHandler)?.value
  153. } else {
  154. let loadedFromCache = retrieveImageFromCache(
  155. source: source,
  156. options: options,
  157. completionHandler: completionHandler)
  158. if loadedFromCache {
  159. return nil
  160. }
  161. if options.onlyFromCache {
  162. let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey))
  163. completionHandler?(.failure(error))
  164. return nil
  165. }
  166. return loadAndCacheImage(
  167. source: source,
  168. options: options,
  169. completionHandler: completionHandler)?.value
  170. }
  171. }
  172. func provideImage(
  173. provider: ImageDataProvider,
  174. options: KingfisherParsedOptionsInfo,
  175. completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)?)
  176. {
  177. guard let completionHandler = completionHandler else { return }
  178. provider.data { result in
  179. switch result {
  180. case .success(let data):
  181. (options.processingQueue ?? self.processingQueue).execute {
  182. let processor = options.processor
  183. let processingItem = ImageProcessItem.data(data)
  184. guard let image = processor.process(item: processingItem, options: options) else {
  185. options.callbackQueue.execute {
  186. let error = KingfisherError.processorError(
  187. reason: .processingFailed(processor: processor, item: processingItem))
  188. completionHandler(.failure(error))
  189. }
  190. return
  191. }
  192. options.callbackQueue.execute {
  193. let result = ImageLoadingResult(image: image, url: nil, originalData: data)
  194. completionHandler(.success(result))
  195. }
  196. }
  197. case .failure(let error):
  198. options.callbackQueue.execute {
  199. let error = KingfisherError.imageSettingError(
  200. reason: .dataProviderError(provider: provider, error: error))
  201. completionHandler(.failure(error))
  202. }
  203. }
  204. }
  205. }
  206. @discardableResult
  207. func loadAndCacheImage(
  208. source: Source,
  209. options: KingfisherParsedOptionsInfo,
  210. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask.WrappedTask?
  211. {
  212. func cacheImage(_ result: Result<ImageLoadingResult, KingfisherError>)
  213. {
  214. switch result {
  215. case .success(let value):
  216. // Add image to cache.
  217. let targetCache = options.targetCache ?? self.cache
  218. targetCache.store(
  219. value.image,
  220. original: value.originalData,
  221. forKey: source.cacheKey,
  222. options: options,
  223. toDisk: !options.cacheMemoryOnly)
  224. {
  225. _ in
  226. if options.waitForCache {
  227. let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
  228. completionHandler?(.success(result))
  229. }
  230. }
  231. // Add original image to cache if necessary.
  232. let needToCacheOriginalImage = options.cacheOriginalImage &&
  233. options.processor != DefaultImageProcessor.default
  234. if needToCacheOriginalImage {
  235. let originalCache = options.originalCache ?? targetCache
  236. originalCache.storeToDisk(
  237. value.originalData,
  238. forKey: source.cacheKey,
  239. processorIdentifier: DefaultImageProcessor.default.identifier,
  240. expiration: options.diskCacheExpiration)
  241. }
  242. if !options.waitForCache {
  243. let result = RetrieveImageResult(image: value.image, cacheType: .none, source: source)
  244. completionHandler?(.success(result))
  245. }
  246. case .failure(let error):
  247. completionHandler?(.failure(error))
  248. }
  249. }
  250. switch source {
  251. case .network(let resource):
  252. let downloader = options.downloader ?? self.downloader
  253. guard let task = downloader.downloadImage(
  254. with: resource.downloadURL,
  255. options: options,
  256. completionHandler: cacheImage) else {
  257. return nil
  258. }
  259. return .download(task)
  260. case .provider(let provider):
  261. provideImage(provider: provider, options: options, completionHandler: cacheImage)
  262. return .dataProviding
  263. }
  264. }
  265. /// Retrieves image from memory or disk cache.
  266. ///
  267. /// - Parameters:
  268. /// - source: The target source from which to get image.
  269. /// - key: The key to use when caching the image.
  270. /// - url: Image request URL. This is not used when retrieving image from cache. It is just used for
  271. /// `RetrieveImageResult` callback compatibility.
  272. /// - options: Options on how to get the image from image cache.
  273. /// - completionHandler: Called when the image retrieving finishes, either with succeeded
  274. /// `RetrieveImageResult` or an error.
  275. /// - Returns: `true` if the requested image or the original image before being processed is existing in cache.
  276. /// Otherwise, this method returns `false`.
  277. ///
  278. /// - Note:
  279. /// The image retrieving could happen in either memory cache or disk cache. The `.processor` option in
  280. /// `options` will be considered when searching in the cache. If no processed image is found, Kingfisher
  281. /// will try to check whether an original version of that image is existing or not. If there is already an
  282. /// original, Kingfisher retrieves it from cache and processes it. Then, the processed image will be store
  283. /// back to cache for later use.
  284. func retrieveImageFromCache(
  285. source: Source,
  286. options: KingfisherParsedOptionsInfo,
  287. completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> Bool
  288. {
  289. // 1. Check whether the image was already in target cache. If so, just get it.
  290. let targetCache = options.targetCache ?? cache
  291. let key = source.cacheKey
  292. let targetImageCached = targetCache.imageCachedType(
  293. forKey: key, processorIdentifier: options.processor.identifier)
  294. let validCache = targetImageCached.cached &&
  295. (options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory)
  296. if validCache {
  297. targetCache.retrieveImage(forKey: key, options: options) { result in
  298. guard let completionHandler = completionHandler else { return }
  299. options.callbackQueue.execute {
  300. result.match(
  301. onSuccess: { cacheResult in
  302. let value: Result<RetrieveImageResult, KingfisherError>
  303. if let image = cacheResult.image {
  304. value = result.map {
  305. RetrieveImageResult(image: image, cacheType: $0.cacheType, source: source)
  306. }
  307. } else {
  308. value = .failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
  309. }
  310. completionHandler(value)
  311. },
  312. onFailure: { _ in
  313. completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
  314. }
  315. )
  316. }
  317. }
  318. return true
  319. }
  320. // 2. Check whether the original image exists. If so, get it, process it, save to storage and return.
  321. let originalCache = options.originalCache ?? targetCache
  322. // No need to store the same file in the same cache again.
  323. if originalCache === targetCache && options.processor == DefaultImageProcessor.default {
  324. return false
  325. }
  326. // Check whether the unprocessed image existing or not.
  327. let originalImageCached = originalCache.imageCachedType(
  328. forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier).cached
  329. if originalImageCached {
  330. // Now we are ready to get found the original image from cache. We need the unprocessed image, so remove
  331. // any processor from options first.
  332. var optionsWithoutProcessor = options
  333. optionsWithoutProcessor.processor = DefaultImageProcessor.default
  334. originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in
  335. result.match(
  336. onSuccess: { cacheResult in
  337. guard let image = cacheResult.image else {
  338. return
  339. }
  340. let processor = options.processor
  341. (options.processingQueue ?? self.processingQueue).execute {
  342. let item = ImageProcessItem.image(image)
  343. guard let processedImage = processor.process(item: item, options: options) else {
  344. let error = KingfisherError.processorError(
  345. reason: .processingFailed(processor: processor, item: item))
  346. options.callbackQueue.execute { completionHandler?(.failure(error)) }
  347. return
  348. }
  349. var cacheOptions = options
  350. cacheOptions.callbackQueue = .untouch
  351. targetCache.store(
  352. processedImage,
  353. forKey: key,
  354. options: cacheOptions,
  355. toDisk: !options.cacheMemoryOnly)
  356. {
  357. _ in
  358. if options.waitForCache {
  359. let value = RetrieveImageResult(image: processedImage, cacheType: .none, source: source)
  360. options.callbackQueue.execute { completionHandler?(.success(value)) }
  361. }
  362. }
  363. if !options.waitForCache {
  364. let value = RetrieveImageResult(image: processedImage, cacheType: .none, source: source)
  365. options.callbackQueue.execute { completionHandler?(.success(value)) }
  366. }
  367. }
  368. },
  369. onFailure: { _ in
  370. // This should not happen actually, since we already confirmed `originalImageCached` is `true`.
  371. // Just in case...
  372. options.callbackQueue.execute {
  373. completionHandler?(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
  374. }
  375. }
  376. )
  377. }
  378. return true
  379. }
  380. return false
  381. }
  382. }