KingfisherOptionsInfo.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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 utilize the enum of option items with values to control certain 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 that can be used in ``KingfisherOptionsInfo``.
  38. public enum KingfisherOptionsInfoItem: Sendable {
  39. /// Kingfisher will utilize the associated ``ImageCache`` object when performing related operations, such as
  40. /// attempting to retrieve cached images and storing downloaded images in it.
  41. case targetCache(ImageCache)
  42. /// The ``ImageCache`` used for storing and retrieving original images.
  43. ///
  44. /// If ``originalCache(_:)`` is specified in the options, it will be given preference for storing and retrieving
  45. /// original images. If there is no ``originalCache(_:)`` option, ``targetCache(_:)`` will be used to
  46. /// store original images as well.
  47. ///
  48. /// When using ``KingfisherManager`` to download and store an image, if ``cacheOriginalImage`` is applied in the
  49. /// options, the original image will be stored in the associated ``ImageCache`` of this option.
  50. ///
  51. /// Simultaneously, if a requested final image (with a processor applied) cannot be found in the ``targetCache(_:)``,
  52. /// Kingfisher will attempt to search for the original image to see if it already exists. If found, it will be
  53. /// utilized and processed with the given processor. This optimization prevents downloading the same image multiple
  54. /// times.
  55. case originalCache(ImageCache)
  56. /// Kingfisher will utilize the associated ``ImageDownloader`` object to download the requested images.
  57. case downloader(ImageDownloader)
  58. /// This enum defines the transition effect to be applied when setting an image to an image view.
  59. ///
  60. /// Kingfisher uses the ``ImageTransition`` specified by this enum to animate the image in if it's downloaded from
  61. /// the web.
  62. ///
  63. /// By default, the transition does not occur when the image is retrieved from either memory or disk cache. To
  64. /// force the transition even when the image is retrieved from the cache, also set
  65. /// ``KingfisherOptionsInfoItem/forceTransition``.
  66. case transition(ImageTransition)
  67. /// The associated `Float` value to be set as the priority of the image download task.
  68. ///
  69. /// This value should fall within the range of 0.0 to 1.0. If this option is not set, the default value
  70. /// (`URLSessionTask.defaultPriority`) will be used.
  71. case downloadPriority(Float)
  72. /// When set, Kingfisher will disregard the cache and attempt to initiate a download task for the image source.
  73. case forceRefresh
  74. /// Sets whether Kingfisher should try to load from memory cache first, and then perform a refresh from network.
  75. ///
  76. /// When set, Kingfisher will attempt to retrieve the image from memory cache first. If the image is not found in
  77. /// the memory cache, it will skip the disk cache and download the image again from the network. This is useful
  78. /// when you want to display a changeable image with the same URL within the same app session, while avoiding
  79. /// multiple downloads.
  80. case fromMemoryCacheOrRefresh
  81. /// When set, applying a transition to set the image in an image view will occur even when the image is retrieved
  82. /// from the cache. Refer to the ``transition(_:)`` option for more details.
  83. case forceTransition
  84. /// When set, Kingfisher will cache the value only in memory and not on disk.
  85. case cacheMemoryOnly
  86. /// When set, Kingfisher will wait for the caching operation to be completed before invoking the completion block.
  87. case waitForCache
  88. /// When set, Kingfisher will attempt to retrieve the image solely from the cache and not from the network.
  89. ///
  90. /// If the image is not found in the cache, the image retrieval will fail with a
  91. /// ``KingfisherError/CacheErrorReason/imageNotExisting(key:)`` error.
  92. case onlyFromCache
  93. /// Decode the image on a background thread before usage.
  94. ///
  95. /// This process involves decoding the downloaded image data and performing off-screen rendering to extract pixel
  96. /// information in the background. While this can accelerate display performance, it may require additional time
  97. /// to prepare the image for use.
  98. case backgroundDecode
  99. /// The associated value will be used as the target queue of dispatch callbacks when retrieving images from
  100. /// cache. If not set, Kingfisher will use `.mainCurrentOrAsync` for callbacks.
  101. ///
  102. /// - Note: This option does not affect the callbacks for UI related extension methods. You will always get the
  103. /// callbacks called from main queue.
  104. /// The associated value will serve as the target queue for dispatch callbacks when retrieving images from the cache.
  105. ///
  106. /// If not set, Kingfisher will use ``CallbackQueue/mainCurrentOrAsync`` for callbacks.
  107. ///
  108. /// - Note: This option does not impact the callbacks for UI-related extension methods. Those callbacks will always
  109. /// occur on the main queue.
  110. case callbackQueue(CallbackQueue)
  111. /// The associated value will be used as the scale factor when converting retrieved image data to an image object.
  112. ///
  113. /// Specify the image scale rather than your screen scale. You should set the correct scale when dealing with 2x or
  114. /// 3x retina images. Otherwise, Kingfisher will convert the data to an image object with a scale of 1.0.
  115. case scaleFactor(CGFloat)
  116. /// Determines whether all the animated image data should be preloaded.
  117. ///
  118. /// The default value is `false`, which means only the following frames will be loaded on demand. If set to `true`,
  119. /// all the animated image data will be loaded and decoded into memory.
  120. ///
  121. /// This option is primarily used for internal backward compatibility. It should not be set directly. Instead, you
  122. /// should choose the appropriate image view class to control the GIF data loading. Kingfisher offers two classes
  123. /// for displaying GIF images: ``AnimatedImageView``, which does not preload all data, consumes less memory, but uses
  124. /// more CPU during display; and a regular image view (`UIImageView` or `NSImageView`), which loads all data at
  125. /// once, consumes more memory, but decodes image frames only once.
  126. case preloadAllAnimationData
  127. /// The contained ``ImageDownloadRequestModifier`` will be used to alter the request before it is sent.
  128. ///
  129. /// This is the final opportunity to modify the image download request. You can customize the request for various
  130. /// purposes, such as adding an authentication token to the header, performing basic HTTP authentication, or URL
  131. /// mapping.
  132. ///
  133. /// By default, the original request is sent without any modifications.
  134. case requestModifier(any AsyncImageDownloadRequestModifier)
  135. /// The contained ``ImageDownloadRedirectHandler`` will be used to alter the request during redirection.
  136. ///
  137. /// This provides an opportunity to customize the image download request during redirection. You can modify the
  138. /// request for various purposes, such as adding an authentication token to the header, performing basic HTTP
  139. /// authentication, or URL mapping.
  140. ///
  141. /// By default, the original redirection request is sent without any modifications.
  142. case redirectHandler(any ImageDownloadRedirectHandler)
  143. /// The processor used in the image retrieval task.
  144. ///
  145. /// After downloading is complete, a processor will convert the downloaded data into an image and/or apply various
  146. /// filters or transformations to it.
  147. ///
  148. /// If a cache is linked to the downloader (which occurs when you use ``KingfisherManager`` or any of the view
  149. /// extension methods), the converted image will also be stored in the cache. If not set, the
  150. /// ``DefaultImageProcessor/default`` will be used.
  151. case processor(any ImageProcessor)
  152. /// Offers a ``CacheSerializer`` to convert data into an image object for retrieval from disk cache, or vice versa
  153. /// for storage in the disk cache.
  154. ///
  155. /// If not set, the ``DefaultCacheSerializer/default`` will be used.
  156. case cacheSerializer(any CacheSerializer)
  157. /// An ``ImageModifier`` for making adjustments to an image right before it is used.
  158. ///
  159. /// If the image was directly fetched from the downloader, the modifier will be applied immediately after the
  160. /// ``ImageProcessor``. If the image is retrieved from a cache, the modifier will be applied after the
  161. /// ``CacheSerializer``.
  162. ///
  163. /// Use the ``ImageModifier`` when you need to set properties that do not persist when caching the image with a
  164. /// specific image type. Examples include setting the `renderingMode` or `alignmentInsets` of a `UIImage`.
  165. case imageModifier(any ImageModifier)
  166. /// Keep the existing image of image view while setting another image to it.
  167. /// By setting this option, the placeholder image parameter of image view extension method
  168. /// will be ignored and the current image will be kept while loading or downloading the new image.
  169. case keepCurrentImageWhileLoading
  170. /// When set, Kingfisher will load only the first frame from an animated image file as a single image.
  171. ///
  172. /// Loading animated images can consume a significant amount of memory. This option is useful when you want to
  173. /// display a static preview of the first frame from an animated image. It will be ignored if the target image is
  174. /// not animated image data.
  175. case onlyLoadFirstFrame
  176. /// When set and an non-default ``ImageProcessor`` is used, Kingfisher will attempt to cache both the final result
  177. /// and the original image.
  178. ///
  179. /// Kingfisher will have the opportunity to use the original image when another processor is applied to the same
  180. /// resource, instead of downloading it anew. You can use ``KingfisherOptionsInfoItem/originalCache(_:)`` to
  181. /// specify a cache for the original images if necessary.
  182. ///
  183. /// The original image will only be cached to disk storage.
  184. case cacheOriginalImage
  185. /// When set and an image retrieval error occurs, Kingfisher will replace the requested image with the provided
  186. /// image (or an empty image).
  187. ///
  188. /// This is useful when you prefer not to display a placeholder during loading but want to use a default image when
  189. /// requests fail.
  190. case onFailureImage(KFCrossPlatformImage?)
  191. /// When set and used in methods of ``ImagePrefetcher``, the prefetching operation will aggressively load the images
  192. /// into memory storage.
  193. ///
  194. /// By default, this option is not included in the options. This means that if the requested image is already in
  195. /// the disk cache, Kingfisher will not attempt to load it into memory.
  196. case alsoPrefetchToMemory
  197. /// When set, disk storage loading will occur in the same calling queue.
  198. ///
  199. /// By default, disk storage file loading operates on its own queue with asynchronous dispatch behavior. While this
  200. /// provides improved non-blocking disk loading performance, it can lead to flickering when you reload an image from
  201. /// disk if the image view already has an image set.
  202. ///
  203. /// Setting this option will eliminate that flickering by keeping all loading in the same queue (typically the UI
  204. /// queue if you are using Kingfisher's extension methods to set an image). However, this comes with a tradeoff in
  205. /// loading performance.
  206. case loadDiskFileSynchronously
  207. /// Options for controlling the data writing process to disk storage.
  208. ///
  209. /// When set, these options will be passed to the store operation for new files.
  210. case diskStoreWriteOptions(Data.WritingOptions)
  211. /// When set, use the associated ``StorageExpiration`` value for the memory cache to determine the expiration date.
  212. ///
  213. /// By default, the underlying ``MemoryStorage/Backend`` uses the expiration defined in its ``MemoryStorage/Config``
  214. /// for all items. If this option is set, the ``MemoryStorage/Backend`` will utilize the associated value to
  215. /// override the configuration setting for this caching item.
  216. case memoryCacheExpiration(StorageExpiration)
  217. /// When set, use the associated ``ExpirationExtending`` value for the memory cache to determine the extending policy
  218. /// when setting the next expiration date.
  219. ///
  220. /// The item's expiration date will be extended after access to keep the "most recently accessed" items alive for a
  221. /// longer duration in the cache.
  222. ///
  223. /// By default, the underlying ``MemoryStorage/Backend`` uses the initial cache expiration as the extending value,
  224. /// which is ``ExpirationExtending/cacheTime``.
  225. ///
  226. /// - Note: To disable expiration extending entirely, use ``ExpirationExtending/none``.
  227. case memoryCacheAccessExtendingExpiration(ExpirationExtending)
  228. /// When set, use the associated ``StorageExpiration`` value for the disk cache to determine the expiration date.
  229. ///
  230. /// By default, the underlying ``DiskStorage/Backend`` uses the expiration defined in its ``DiskStorage/Config``
  231. /// for all items. If this option is set, the ``DiskStorage/Backend`` will utilize the associated value to override
  232. /// the configuration setting for this caching item.
  233. case diskCacheExpiration(StorageExpiration)
  234. /// When set, use the associated ``ExpirationExtending`` value for the disk cache to determine the extending policy
  235. /// when setting the next expiration date.
  236. ///
  237. /// The item's expiration date will be extended after access to keep the "most recently accessed" items alive for a
  238. /// longer duration in the cache.
  239. ///
  240. /// By default, the underlying ``DiskStorage/Backend`` uses the initial cache expiration as the extending value,
  241. /// which is ``ExpirationExtending/cacheTime``.
  242. ///
  243. /// - Note: To disable expiration extending entirely, use ``ExpirationExtending/none``.
  244. case diskCacheAccessExtendingExpiration(ExpirationExtending)
  245. /// Determines the queue on which image processing should occur.
  246. ///
  247. /// By default, Kingfisher uses an internal pre-defined serial queue to process images. Use this option to modify
  248. /// this behavior. For instance, you can specify ``CallbackQueue/mainCurrentOrAsync`` to process the image on the
  249. /// main queue, preventing potential flickering (but with the risk of blocking the UI, especially if the processor
  250. /// is time-consuming).
  251. case processingQueue(CallbackQueue)
  252. /// Enables progressive image loading.
  253. ///
  254. /// Kingfisher will use the associated ``ImageProgressive`` value to process progressive JPEG data and display
  255. /// it progressively, if the image supports it.
  256. case progressiveJPEG(ImageProgressive)
  257. /// Sets a set of alternative sources when the original input `Source` fails to load.
  258. ///
  259. /// The `Source`s in the associated
  260. /// array will be used to start a new image loading task if the previous task fails due to an error. The image
  261. /// source loading process will stop as soon as a source is loaded successfully. If all `[Source]`s are used but
  262. /// the loading is still failing, an `imageSettingError` with `alternativeSourcesExhausted` as its reason will be
  263. /// thrown out.
  264. ///
  265. /// This option is useful if you want to implement a fallback solution for setting image.
  266. ///
  267. /// User cancellation will not trigger the alternative source loading.
  268. ///
  269. /// Specifies a set of alternative sources to use when the original input ``Source`` fails to load.
  270. ///
  271. /// The ``Source``s in the associated array will be used to start a new image loading task if the previous task
  272. /// fails due to an error. The image source loading process will halt as soon as a source is loaded successfully.
  273. /// If all ``Source``s are used, but loading still fails, a
  274. /// ``KingfisherError/ImageSettingErrorReason/alternativeSourcesExhausted(_:)``will be used as the error in the
  275. /// result.
  276. ///
  277. /// This option is useful for implementing a fallback solution for image setting.
  278. ///
  279. /// - Note: User cancellation will not trigger the loading of alternative sources.
  280. case alternativeSources([Source])
  281. /// Provides a retry strategy to use when something goes wrong during the image retrieval process from
  282. /// ``KingfisherManager``.
  283. ///
  284. /// You can define a strategy by creating a type that conforms to the ``RetryStrategy`` protocol. When Kingfisher
  285. /// encounters a loading failure, it follows the defined retry strategy and retries until a ``RetryDecision/stop``
  286. /// is received.
  287. ///
  288. /// - Note: All extension methods of Kingfisher (the `kf` extensions on `UIImageView` or `UIButton`, for example)
  289. /// retrieve images through ``KingfisherManager``, so the retry strategy also applies when using them. However,
  290. /// this option does not apply when passed to an ``ImageDownloader`` or an ``ImageCache`` directly.
  291. case retryStrategy(any RetryStrategy)
  292. /// Specifies the `Source` to load when the user enables Low Data Mode and the original source fails due to the data
  293. /// constraint.
  294. ///
  295. /// When the user enables Low Data Mode in the system settings, and the original source fails with an
  296. /// `NSURLErrorNetworkUnavailableReason.constrained` error, Kingfisher uses this source instead to load an image
  297. /// for Low Data Mode.
  298. ///
  299. /// When this option is set, the `allowsConstrainedNetworkAccess` property of the request for the original source
  300. /// will be set to `false`, and the ``Source`` in the associated value will be used to retrieve the image for Low
  301. /// Data Mode. Typically, you can provide a low-resolution version of your image or a local image provider to
  302. /// display a placeholder to save data usage.
  303. ///
  304. /// If not set or if the associated optional ``Source`` value is `nil`, the device's Low Data Mode will be ignored,
  305. /// and the original source will be loaded following the system default behavior.
  306. case lowDataMode(Source?)
  307. case forcedCacheFileExtension(String?)
  308. }
  309. // MARK: - KingfisherParsedOptionsInfo
  310. // Improve performance by parsing the input `KingfisherOptionsInfo` (self) first.
  311. // So we can prevent the iterating over the options array again and again.
  312. /// Represents the parsed options info used throughout Kingfisher methods.
  313. ///
  314. /// Each property in this type corresponds to a case member in ``KingfisherOptionsInfoItem``. When a
  315. /// ``KingfisherOptionsInfo`` is sent to Kingfisher-related methods, it will be parsed and converted to a
  316. /// ``KingfisherParsedOptionsInfo`` first before passing through the internal methods.
  317. public struct KingfisherParsedOptionsInfo: Sendable {
  318. public var targetCache: ImageCache? = nil
  319. public var originalCache: ImageCache? = nil
  320. public var downloader: ImageDownloader? = nil
  321. public var transition: ImageTransition = .none
  322. public var downloadPriority: Float = URLSessionTask.defaultPriority
  323. public var forceRefresh = false
  324. public var fromMemoryCacheOrRefresh = false
  325. public var forceTransition = false
  326. public var cacheMemoryOnly = false
  327. public var waitForCache = false
  328. public var onlyFromCache = false
  329. public var backgroundDecode = false
  330. public var preloadAllAnimationData = false
  331. public var callbackQueue: CallbackQueue = .mainCurrentOrAsync
  332. public var scaleFactor: CGFloat = 1.0
  333. public var requestModifier: (any AsyncImageDownloadRequestModifier)? = nil
  334. public var redirectHandler: (any ImageDownloadRedirectHandler)? = nil
  335. public var processor: any ImageProcessor = DefaultImageProcessor.default
  336. public var imageModifier: (any ImageModifier)? = nil
  337. public var cacheSerializer: any CacheSerializer = DefaultCacheSerializer.default
  338. public var keepCurrentImageWhileLoading = false
  339. public var onlyLoadFirstFrame = false
  340. public var cacheOriginalImage = false
  341. public var onFailureImage: Optional<KFCrossPlatformImage?> = .none
  342. public var alsoPrefetchToMemory = false
  343. public var loadDiskFileSynchronously = false
  344. public var diskStoreWriteOptions: Data.WritingOptions = []
  345. public var memoryCacheExpiration: StorageExpiration? = nil
  346. public var memoryCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
  347. public var diskCacheExpiration: StorageExpiration? = nil
  348. public var diskCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
  349. public var processingQueue: CallbackQueue? = nil
  350. public var progressiveJPEG: ImageProgressive? = nil
  351. public var alternativeSources: [Source]? = nil
  352. public var retryStrategy: (any RetryStrategy)? = nil
  353. public var lowDataModeSource: Source? = nil
  354. public var forcedExtension: String? = nil
  355. var onDataReceived: [any DataReceivingSideEffect]? = nil
  356. public init(_ info: KingfisherOptionsInfo?) {
  357. guard let info = info else { return }
  358. for option in info {
  359. switch option {
  360. case .targetCache(let value): targetCache = value
  361. case .originalCache(let value): originalCache = value
  362. case .downloader(let value): downloader = value
  363. case .transition(let value): transition = value
  364. case .downloadPriority(let value): downloadPriority = value
  365. case .forceRefresh: forceRefresh = true
  366. case .fromMemoryCacheOrRefresh: fromMemoryCacheOrRefresh = true
  367. case .forceTransition: forceTransition = true
  368. case .cacheMemoryOnly: cacheMemoryOnly = true
  369. case .waitForCache: waitForCache = true
  370. case .onlyFromCache: onlyFromCache = true
  371. case .backgroundDecode: backgroundDecode = true
  372. case .preloadAllAnimationData: preloadAllAnimationData = true
  373. case .callbackQueue(let value): callbackQueue = value
  374. case .scaleFactor(let value): scaleFactor = value
  375. case .requestModifier(let value): requestModifier = value
  376. case .redirectHandler(let value): redirectHandler = value
  377. case .processor(let value): processor = value
  378. case .imageModifier(let value): imageModifier = value
  379. case .cacheSerializer(let value): cacheSerializer = value
  380. case .keepCurrentImageWhileLoading: keepCurrentImageWhileLoading = true
  381. case .onlyLoadFirstFrame: onlyLoadFirstFrame = true
  382. case .cacheOriginalImage: cacheOriginalImage = true
  383. case .onFailureImage(let value): onFailureImage = .some(value)
  384. case .alsoPrefetchToMemory: alsoPrefetchToMemory = true
  385. case .loadDiskFileSynchronously: loadDiskFileSynchronously = true
  386. case .diskStoreWriteOptions(let options): diskStoreWriteOptions = options
  387. case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration
  388. case .memoryCacheAccessExtendingExpiration(let expirationExtending): memoryCacheAccessExtendingExpiration = expirationExtending
  389. case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration
  390. case .diskCacheAccessExtendingExpiration(let expirationExtending): diskCacheAccessExtendingExpiration = expirationExtending
  391. case .processingQueue(let queue): processingQueue = queue
  392. case .progressiveJPEG(let value): progressiveJPEG = value
  393. case .alternativeSources(let sources): alternativeSources = sources
  394. case .retryStrategy(let strategy): retryStrategy = strategy
  395. case .lowDataMode(let source): lowDataModeSource = source
  396. case .forcedCacheFileExtension(let ext): forcedExtension = ext
  397. }
  398. }
  399. if originalCache == nil {
  400. originalCache = targetCache
  401. }
  402. }
  403. }
  404. extension KingfisherParsedOptionsInfo {
  405. var imageCreatingOptions: ImageCreatingOptions {
  406. return ImageCreatingOptions(
  407. scale: scaleFactor,
  408. duration: 0.0,
  409. preloadAll: preloadAllAnimationData,
  410. onlyFirstFrame: onlyLoadFirstFrame)
  411. }
  412. }
  413. protocol DataReceivingSideEffect: AnyObject, Sendable {
  414. var onShouldApply: () -> Bool { get set }
  415. func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data)
  416. }
  417. class ImageLoadingProgressSideEffect: DataReceivingSideEffect, @unchecked Sendable {
  418. private let propertyQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageLoadingProgressSideEffectPropertyQueue")
  419. private var _onShouldApply: () -> Bool = { return true }
  420. var onShouldApply: () -> Bool {
  421. get { propertyQueue.sync { _onShouldApply } }
  422. set { propertyQueue.sync { _onShouldApply = newValue } }
  423. }
  424. let block: DownloadProgressBlock
  425. init(_ block: @escaping DownloadProgressBlock) {
  426. self.block = block
  427. }
  428. func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) {
  429. DispatchQueue.main.async {
  430. guard self.onShouldApply() else { return }
  431. guard let expectedContentLength = task.task.response?.expectedContentLength,
  432. expectedContentLength != -1 else
  433. {
  434. return
  435. }
  436. let dataLength = Int64(task.mutableData.count)
  437. self.block(dataLength, expectedContentLength)
  438. }
  439. }
  440. }