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.

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