KingfisherManager.swift 18 KB

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