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.

373 lines
21 KiB

  1. //
  2. // KingfisherOptionsInfo.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 15/4/23.
  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. #if os(macOS)
  27. import AppKit
  28. #else
  29. import UIKit
  30. #endif
  31. /// KingfisherOptionsInfo is a typealias for [KingfisherOptionsInfoItem].
  32. /// You can use the enum of option item with value to control some behaviors of Kingfisher.
  33. public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem]
  34. extension Array where Element == KingfisherOptionsInfoItem {
  35. static let empty: KingfisherOptionsInfo = []
  36. }
  37. /// Represents the available option items could be used in `KingfisherOptionsInfo`.
  38. public enum KingfisherOptionsInfoItem {
  39. /// Kingfisher will use the associated `ImageCache` object when handling related operations,
  40. /// including trying to retrieve the cached images and store the downloaded image to it.
  41. case targetCache(ImageCache)
  42. /// The `ImageCache` for storing and retrieving original images. If `originalCache` is
  43. /// contained in the options, it will be preferred for storing and retrieving original images.
  44. /// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images.
  45. ///
  46. /// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is
  47. /// applied in the option, the original image will be stored to this `originalCache`. At the
  48. /// same time, if a requested final image (with processor applied) cannot be found in `targetCache`,
  49. /// Kingfisher will try to search the original image to check whether it is already there. If found,
  50. /// it will be used and applied with the given processor. It is an optimization for not downloading
  51. /// the same image for multiple times.
  52. case originalCache(ImageCache)
  53. /// Kingfisher will use the associated `ImageDownloader` object to download the requested images.
  54. case downloader(ImageDownloader)
  55. /// Member for animation transition when using `UIImageView`. Kingfisher will use the `ImageTransition` of
  56. /// this enum to animate the image in if it is downloaded from web. The transition will not happen when the
  57. /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
  58. /// the image being retrieved from cache, set `.forceRefresh` as well.
  59. case transition(ImageTransition)
  60. /// Associated `Float` value will be set as the priority of image download task. The value for it should be
  61. /// between 0.0~1.0. If this option not set, the default value (`URLSessionTask.defaultPriority`) will be used.
  62. case downloadPriority(Float)
  63. /// If set, Kingfisher will ignore the cache and try to fire a download task for the resource.
  64. case forceRefresh
  65. /// If set, Kingfisher will try to retrieve the image from memory cache first. If the image is not in memory
  66. /// cache, then it will ignore the disk cache but download the image again from network. This is useful when
  67. /// you want to display a changeable image behind the same url at the same app session, while avoiding download
  68. /// it for multiple times.
  69. case fromMemoryCacheOrRefresh
  70. /// If set, setting the image to an image view will happen with transition even when retrieved from cache.
  71. /// See `.transition` option for more.
  72. case forceTransition
  73. /// If set, Kingfisher will only cache the value in memory but not in disk.
  74. case cacheMemoryOnly
  75. /// If set, Kingfisher will wait for caching operation to be completed before calling the completion block.
  76. case waitForCache
  77. /// If set, Kingfisher will only try to retrieve the image from cache, but not from network. If the image is
  78. /// not in cache, the image retrieving will fail with an error.
  79. case onlyFromCache
  80. /// Decode the image in background thread before using. It will decode the downloaded image data and do a off-screen
  81. /// rendering to extract pixel information in background. This can speed up display, but will cost more time to
  82. /// prepare the image for using.
  83. case backgroundDecode
  84. /// The associated value of this member will be used as the target queue of dispatch callbacks when
  85. /// retrieving images from cache. If not set, Kingfisher will use main queue for callbacks.
  86. @available(*, deprecated, message: "Use `.callbackQueue(CallbackQueue)` instead.")
  87. case callbackDispatchQueue(DispatchQueue?)
  88. /// The associated value will be used as the target queue of dispatch callbacks when retrieving images from
  89. /// cache. If not set, Kingfisher will use `.mainCurrentOrAsync` for callbacks.
  90. ///
  91. /// - Note:
  92. /// This option does not affect the callbacks for UI related extension methods. You will always get the
  93. /// callbacks called from main queue.
  94. case callbackQueue(CallbackQueue)
  95. /// The associated value will be used as the scale factor when converting retrieved data to an image.
  96. /// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing
  97. /// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0.
  98. case scaleFactor(CGFloat)
  99. /// Whether all the animated image data should be preloaded. Default is `false`, which means only following frames
  100. /// will be loaded on need. If `true`, all the animated image data will be loaded and decoded into memory.
  101. ///
  102. /// This option is mainly used for back compatibility internally. You should not set it directly. Instead,
  103. /// you should choose the image view class to control the GIF data loading. There are two classes in Kingfisher
  104. /// support to display a GIF image. `AnimatedImageView` does not preload all data, it takes much less memory, but
  105. /// uses more CPU when display. While a normal image view (`UIImageView` or `NSImageView`) loads all data at once,
  106. /// which uses more memory but only decode image frames once.
  107. case preloadAllAnimationData
  108. /// The `ImageDownloadRequestModifier` contained will be used to change the request before it being sent.
  109. /// This is the last chance you can modify the image download request. You can modify the request for some
  110. /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping.
  111. /// The original request will be sent without any modification by default.
  112. case requestModifier(ImageDownloadRequestModifier)
  113. /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection.
  114. /// This is the possibility you can modify the image download request during redirect. You can modify the request for
  115. /// some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url
  116. /// mapping.
  117. /// The original redirection request will be sent without any modification by default.
  118. case redirectHandler(ImageDownloadRedirectHandler)
  119. /// Processor for processing when the downloading finishes, a processor will convert the downloaded data to an image
  120. /// and/or apply some filter on it. If a cache is connected to the downloader (it happens when you are using
  121. /// KingfisherManager or any of the view extension methods), the converted image will also be sent to cache as well.
  122. /// If not set, the `DefaultImageProcessor.default` will be used.
  123. case processor(ImageProcessor)
  124. /// Supplies a `CacheSerializer` to convert some data to an image object for
  125. /// retrieving from disk cache or vice versa for storing to disk cache.
  126. /// If not set, the `DefaultCacheSerializer.default` will be used.
  127. case cacheSerializer(CacheSerializer)
  128. /// An `ImageModifier` is for modifying an image as needed right before it is used. If the image was fetched
  129. /// directly from the downloader, the modifier will run directly after the `ImageProcessor`. If the image is being
  130. /// fetched from a cache, the modifier will run after the `CacheSerializer`.
  131. ///
  132. /// Use `ImageModifier` when you need to set properties that do not persist when caching the image on a concrete
  133. /// type of `Image`, such as the `renderingMode` or the `alignmentInsets` of `UIImage`.
  134. case imageModifier(ImageModifier)
  135. /// Keep the existing image of image view while setting another image to it.
  136. /// By setting this option, the placeholder image parameter of image view extension method
  137. /// will be ignored and the current image will be kept while loading or downloading the new image.
  138. case keepCurrentImageWhileLoading
  139. /// If set, Kingfisher will only load the first frame from an animated image file as a single image.
  140. /// Loading an animated images may take too much memory. It will be useful when you want to display a
  141. /// static preview of the first frame from a animated image.
  142. ///
  143. /// This option will be ignored if the target image is not animated image data.
  144. case onlyLoadFirstFrame
  145. /// If set and an `ImageProcessor` is used, Kingfisher will try to cache both the final result and original
  146. /// image. Kingfisher will have a chance to use the original image when another processor is applied to the same
  147. /// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original
  148. /// images if necessary.
  149. ///
  150. /// The original image will be only cached to disk storage.
  151. case cacheOriginalImage
  152. /// If set and a downloading error occurred Kingfisher will set provided image (or empty)
  153. /// in place of requested one. It's useful when you don't want to show placeholder
  154. /// during loading time but wants to use some default image when requests will be failed.
  155. case onFailureImage(KFCrossPlatformImage?)
  156. /// If set and used in `ImagePrefetcher`, the prefetching operation will load the images into memory storage
  157. /// aggressively. By default this is not contained in the options, that means if the requested image is already
  158. /// in disk cache, Kingfisher will not try to load it to memory.
  159. case alsoPrefetchToMemory
  160. /// If set, the disk storage loading will happen in the same calling queue. By default, disk storage file loading
  161. /// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk
  162. /// loading performance, it also causes a flickering when you reload an image from disk, if the image view already
  163. /// has an image set.
  164. ///
  165. /// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue
  166. /// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance.
  167. case loadDiskFileSynchronously
  168. /// The expiration setting for memory cache. By default, the underlying `MemoryStorage.Backend` uses the
  169. /// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this associated
  170. /// value to overwrite the config setting for this caching item.
  171. case memoryCacheExpiration(StorageExpiration)
  172. /// The expiration extending setting for memory cache. The item expiration time will be incremented by this
  173. /// value after access.
  174. /// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending
  175. /// value: .cacheTime.
  176. ///
  177. /// To disable extending option at all add memoryCacheAccessExtendingExpiration(.none) to options.
  178. case memoryCacheAccessExtendingExpiration(ExpirationExtending)
  179. /// The expiration setting for disk cache. By default, the underlying `DiskStorage.Backend` uses the
  180. /// expiration in its config for all items. If set, the `DiskStorage.Backend` will use this associated
  181. /// value to overwrite the config setting for this caching item.
  182. case diskCacheExpiration(StorageExpiration)
  183. /// The expiration extending setting for disk cache. The item expiration time will be incremented by this value after access.
  184. /// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending value: .cacheTime.
  185. /// To disable extending option at all add diskCacheAccessExtendingExpiration(.none) to options.
  186. case diskCacheAccessExtendingExpiration(ExpirationExtending)
  187. /// Decides on which queue the image processing should happen. By default, Kingfisher uses a pre-defined serial
  188. /// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync`
  189. /// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of
  190. /// blocking the UI, especially if the processor needs a lot of time to run).
  191. case processingQueue(CallbackQueue)
  192. /// Enable progressive image loading, Kingfisher will use the `ImageProgressive` of
  193. case progressiveJPEG(ImageProgressive)
  194. /// The alternative sources will be used when the original input `Source` fails. The `Source`s in the associated
  195. /// array will be used to start a new image loading task if the previous task fails due to an error. The image
  196. /// source loading process will stop as soon as a source is loaded successfully. If all `[Source]`s are used but
  197. /// the loading is still failing, an `imageSettingError` with `alternativeSourcesExhausted` as its reason will be
  198. /// thrown out.
  199. ///
  200. /// This option is useful if you want to implement a fallback solution for setting image.
  201. ///
  202. /// User cancellation will not trigger the alternative source loading.
  203. case alternativeSources([Source])
  204. }
  205. // Improve performance by parsing the input `KingfisherOptionsInfo` (self) first.
  206. // So we can prevent the iterating over the options array again and again.
  207. /// The parsed options info used across Kingfisher methods. Each property in this type corresponds a case member
  208. /// in `KingfisherOptionsInfoItem`. When a `KingfisherOptionsInfo` sent to Kingfisher related methods, it will be
  209. /// parsed and converted to a `KingfisherParsedOptionsInfo` first, and pass through the internal methods.
  210. public struct KingfisherParsedOptionsInfo {
  211. public var targetCache: ImageCache? = nil
  212. public var originalCache: ImageCache? = nil
  213. public var downloader: ImageDownloader? = nil
  214. public var transition: ImageTransition = .none
  215. public var downloadPriority: Float = URLSessionTask.defaultPriority
  216. public var forceRefresh = false
  217. public var fromMemoryCacheOrRefresh = false
  218. public var forceTransition = false
  219. public var cacheMemoryOnly = false
  220. public var waitForCache = false
  221. public var onlyFromCache = false
  222. public var backgroundDecode = false
  223. public var preloadAllAnimationData = false
  224. public var callbackQueue: CallbackQueue = .mainCurrentOrAsync
  225. public var scaleFactor: CGFloat = 1.0
  226. public var requestModifier: ImageDownloadRequestModifier? = nil
  227. public var redirectHandler: ImageDownloadRedirectHandler? = nil
  228. public var processor: ImageProcessor = DefaultImageProcessor.default
  229. public var imageModifier: ImageModifier? = nil
  230. public var cacheSerializer: CacheSerializer = DefaultCacheSerializer.default
  231. public var keepCurrentImageWhileLoading = false
  232. public var onlyLoadFirstFrame = false
  233. public var cacheOriginalImage = false
  234. public var onFailureImage: Optional<KFCrossPlatformImage?> = .none
  235. public var alsoPrefetchToMemory = false
  236. public var loadDiskFileSynchronously = false
  237. public var memoryCacheExpiration: StorageExpiration? = nil
  238. public var memoryCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
  239. public var diskCacheExpiration: StorageExpiration? = nil
  240. public var diskCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
  241. public var processingQueue: CallbackQueue? = nil
  242. public var progressiveJPEG: ImageProgressive? = nil
  243. public var alternativeSources: [Source]? = nil
  244. var onDataReceived: [DataReceivingSideEffect]? = nil
  245. public init(_ info: KingfisherOptionsInfo?) {
  246. guard let info = info else { return }
  247. for option in info {
  248. switch option {
  249. case .targetCache(let value): targetCache = value
  250. case .originalCache(let value): originalCache = value
  251. case .downloader(let value): downloader = value
  252. case .transition(let value): transition = value
  253. case .downloadPriority(let value): downloadPriority = value
  254. case .forceRefresh: forceRefresh = true
  255. case .fromMemoryCacheOrRefresh: fromMemoryCacheOrRefresh = true
  256. case .forceTransition: forceTransition = true
  257. case .cacheMemoryOnly: cacheMemoryOnly = true
  258. case .waitForCache: waitForCache = true
  259. case .onlyFromCache: onlyFromCache = true
  260. case .backgroundDecode: backgroundDecode = true
  261. case .preloadAllAnimationData: preloadAllAnimationData = true
  262. case .callbackQueue(let value): callbackQueue = value
  263. case .scaleFactor(let value): scaleFactor = value
  264. case .requestModifier(let value): requestModifier = value
  265. case .redirectHandler(let value): redirectHandler = value
  266. case .processor(let value): processor = value
  267. case .imageModifier(let value): imageModifier = value
  268. case .cacheSerializer(let value): cacheSerializer = value
  269. case .keepCurrentImageWhileLoading: keepCurrentImageWhileLoading = true
  270. case .onlyLoadFirstFrame: onlyLoadFirstFrame = true
  271. case .cacheOriginalImage: cacheOriginalImage = true
  272. case .onFailureImage(let value): onFailureImage = .some(value)
  273. case .alsoPrefetchToMemory: alsoPrefetchToMemory = true
  274. case .loadDiskFileSynchronously: loadDiskFileSynchronously = true
  275. case .callbackDispatchQueue(let value): callbackQueue = value.map { .dispatch($0) } ?? .mainCurrentOrAsync
  276. case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration
  277. case .memoryCacheAccessExtendingExpiration(let expirationExtending): memoryCacheAccessExtendingExpiration = expirationExtending
  278. case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration
  279. case .diskCacheAccessExtendingExpiration(let expirationExtending): diskCacheAccessExtendingExpiration = expirationExtending
  280. case .processingQueue(let queue): processingQueue = queue
  281. case .progressiveJPEG(let value): progressiveJPEG = value
  282. case .alternativeSources(let sources): alternativeSources = sources
  283. }
  284. }
  285. if originalCache == nil {
  286. originalCache = targetCache
  287. }
  288. }
  289. }
  290. extension KingfisherParsedOptionsInfo {
  291. var imageCreatingOptions: ImageCreatingOptions {
  292. return ImageCreatingOptions(
  293. scale: scaleFactor,
  294. duration: 0.0,
  295. preloadAll: preloadAllAnimationData,
  296. onlyFirstFrame: onlyLoadFirstFrame)
  297. }
  298. }
  299. protocol DataReceivingSideEffect: AnyObject {
  300. var onShouldApply: () -> Bool { get set }
  301. func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data)
  302. }
  303. class ImageLoadingProgressSideEffect: DataReceivingSideEffect {
  304. var onShouldApply: () -> Bool = { return true }
  305. let block: DownloadProgressBlock
  306. init(_ block: @escaping DownloadProgressBlock) {
  307. self.block = block
  308. }
  309. func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) {
  310. DispatchQueue.main.async {
  311. guard self.onShouldApply() else { return }
  312. guard let expectedContentLength = task.task.response?.expectedContentLength,
  313. expectedContentLength != -1 else
  314. {
  315. return
  316. }
  317. let dataLength = Int64(task.mutableData.count)
  318. self.block(dataLength, expectedContentLength)
  319. }
  320. }
  321. }