// // KingfisherOptionsInfo.swift // Kingfisher // // Created by Wei Wang on 15/4/23. // // Copyright (c) 2018 Wei Wang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if os(macOS) import AppKit #else import UIKit #endif /** * KingfisherOptionsInfo is a typealias for [KingfisherOptionsInfoItem]. You can use the enum of option item with value to control some behaviors of Kingfisher. */ public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem] let KingfisherEmptyOptionsInfo = [KingfisherOptionsInfoItem]() /** Items could be added into KingfisherOptionsInfo. */ public enum KingfisherOptionsInfoItem { /// The associated value of this member should be an ImageCache object. Kingfisher will use the specified /// cache object when handling related operations, including trying to retrieve the cached images and store /// the downloaded image to it. case targetCache(ImageCache) /// Cache for storing and retrieving original image. /// Preferred prior to targetCache for storing and retrieving original images if specified. /// Only used if a non-default image processor is involved. case originalCache(ImageCache) /// The associated value of this member should be an ImageDownloader object. Kingfisher will use this /// downloader to download the images. case downloader(ImageDownloader) /// Member for animation transition when using UIImageView. Kingfisher will use the `ImageTransition` of /// this enum to animate the image in if it is downloaded from web. The transition will not happen when the /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when /// the image being retrieved from cache, set `ForceTransition` as well. case transition(ImageTransition) /// Associated `Float` value will be set as the priority of image download task. The value for it should be /// between 0.0~1.0. If this option not set, the default value (`NSURLSessionTaskPriorityDefault`) will be used. case downloadPriority(Float) /// If set, `Kingfisher` will ignore the cache and try to fire a download task for the resource. case forceRefresh /// If set, `Kingfisher` will try to retrieve the image from memory cache first. If the image is not in memory /// cache, then it will ignore the disk cache but download the image again from network. This is useful when /// you want to display a changeable image behind the same url, while avoiding download it again and again. case fromMemoryCacheOrRefresh /// If set, setting the image to an image view will happen with transition even when retrieved from cache. /// See `Transition` option for more. case forceTransition /// If set, `Kingfisher` will only cache the value in memory but not in disk. case cacheMemoryOnly /// If set, `Kingfisher` will only try to retrieve the image from cache not from network. case onlyFromCache /// Decode the image in background thread before using. case backgroundDecode /// The associated value of this member will be used as the target queue of dispatch callbacks when /// retrieving images from cache. If not set, `Kingfisher` will use main quese for callbacks. case callbackDispatchQueue(DispatchQueue?) /// The associated value of this member will be used as the scale factor when converting retrieved data to an image. /// It is the image scale, instead of your screen scale. You may need to specify the correct scale when you dealing /// with 2x or 3x retina images. case scaleFactor(CGFloat) /// Whether all the animated image data should be preloaded. Default it false, which means following frames will be /// loaded on need. If true, all the animated image data will be loaded and decoded into memory. This option is mainly /// used for back compatibility internally. You should not set it directly. `AnimatedImageView` will not preload /// all data, while a normal image view (`UIImageView` or `NSImageView`) will load all data. Choose to use /// corresponding image view type instead of setting this option. case preloadAllAnimationData /// The `ImageDownloadRequestModifier` contained will be used to change the request before it being sent. /// This is the last chance you can modify the request. You can modify the request for some customizing purpose, /// such as adding auth token to the header, do basic HTTP auth or something like url mapping. The original request /// will be sent without any modification by default. case requestModifier(ImageDownloadRequestModifier) /// Processor for processing when the downloading finishes, a processor will convert the downloaded data to an image /// and/or apply some filter on it. If a cache is connected to the downloader (it happens when you are using /// KingfisherManager or the image extension methods), the converted image will also be sent to cache as well as the /// image view. `DefaultImageProcessor.default` will be used by default. case processor(ImageProcessor) /// Supply an `CacheSerializer` to convert some data to an image object for /// retrieving from disk cache or vice versa for storing to disk cache. /// `DefaultCacheSerializer.default` will be used by default. case cacheSerializer(CacheSerializer) /// Modifier for modifying an image right before it is used. /// If the image was fetched directly from the downloader, the modifier will /// run directly after the processor. /// If the image is being fetched from a cache, the modifier will run after /// the cacheSerializer. /// Use `ImageModifier` when you need to set properties on a concrete type /// of `Image`, such as a `UIImage`, that do not persist when caching the image. case imageModifier(ImageModifier) /// Keep the existing image while setting another image to an image view. /// By setting this option, the placeholder image parameter of imageview extension method /// will be ignored and the current image will be kept while loading or downloading the new image. case keepCurrentImageWhileLoading /// If set, Kingfisher will only load the first frame from a animated image data file as a single image. /// Loading a lot of animated images may take too much memory. It will be useful when you want to display a /// static preview of the first frame from a animated image. /// This option will be ignored if the target image is not animated image data. case onlyLoadFirstFrame /// If set and an `ImageProcessor` is used, Kingfisher will try to cache both /// the final result and original image. Kingfisher will have a chance to use /// the original image when another processor is applied to the same resouce, /// instead of downloading it again. case cacheOriginalImage } precedencegroup ItemComparisonPrecedence { associativity: none higherThan: LogicalConjunctionPrecedence } infix operator <== : ItemComparisonPrecedence // This operator returns true if two `KingfisherOptionsInfoItem` enum is the same, without considering the associated values. func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Bool { switch (lhs, rhs) { case (.targetCache(_), .targetCache(_)): return true case (.originalCache(_), .originalCache(_)): return true case (.downloader(_), .downloader(_)): return true case (.transition(_), .transition(_)): return true case (.downloadPriority(_), .downloadPriority(_)): return true case (.forceRefresh, .forceRefresh): return true case (.fromMemoryCacheOrRefresh, .fromMemoryCacheOrRefresh): return true case (.forceTransition, .forceTransition): return true case (.cacheMemoryOnly, .cacheMemoryOnly): return true case (.onlyFromCache, .onlyFromCache): return true case (.backgroundDecode, .backgroundDecode): return true case (.callbackDispatchQueue(_), .callbackDispatchQueue(_)): return true case (.scaleFactor(_), .scaleFactor(_)): return true case (.preloadAllAnimationData, .preloadAllAnimationData): return true case (.requestModifier(_), .requestModifier(_)): return true case (.processor(_), .processor(_)): return true case (.cacheSerializer(_), .cacheSerializer(_)): return true case (.imageModifier(_), .imageModifier(_)): return true case (.keepCurrentImageWhileLoading, .keepCurrentImageWhileLoading): return true case (.onlyLoadFirstFrame, .onlyLoadFirstFrame): return true case (.cacheOriginalImage, .cacheOriginalImage): return true default: return false } } extension Collection where Iterator.Element == KingfisherOptionsInfoItem { func lastMatchIgnoringAssociatedValue(_ target: Iterator.Element) -> Iterator.Element? { return reversed().first { $0 <== target } } func removeAllMatchesIgnoringAssociatedValue(_ target: Iterator.Element) -> [Iterator.Element] { return filter { !($0 <== target) } } } public extension Collection where Iterator.Element == KingfisherOptionsInfoItem { /// The target `ImageCache` which is used. public var targetCache: ImageCache { if let item = lastMatchIgnoringAssociatedValue(.targetCache(.default)), case .targetCache(let cache) = item { return cache } return ImageCache.default } /// The original `ImageCache` which is used. public var originalCache: ImageCache { if let item = lastMatchIgnoringAssociatedValue(.originalCache(.default)), case .originalCache(let cache) = item { return cache } return targetCache } /// The `ImageDownloader` which is specified. public var downloader: ImageDownloader { if let item = lastMatchIgnoringAssociatedValue(.downloader(.default)), case .downloader(let downloader) = item { return downloader } return ImageDownloader.default } /// Member for animation transition when using UIImageView. public var transition: ImageTransition { if let item = lastMatchIgnoringAssociatedValue(.transition(.none)), case .transition(let transition) = item { return transition } return ImageTransition.none } /// A `Float` value set as the priority of image download task. The value for it should be /// between 0.0~1.0. public var downloadPriority: Float { if let item = lastMatchIgnoringAssociatedValue(.downloadPriority(0)), case .downloadPriority(let priority) = item { return priority } return URLSessionTask.defaultPriority } /// Whether an image will be always downloaded again or not. public var forceRefresh: Bool { return contains{ $0 <== .forceRefresh } } /// Whether an image should be got only from memory cache or download. public var fromMemoryCacheOrRefresh: Bool { return contains{ $0 <== .fromMemoryCacheOrRefresh } } /// Whether the transition should always happen or not. public var forceTransition: Bool { return contains{ $0 <== .forceTransition } } /// Whether cache the image only in memory or not. public var cacheMemoryOnly: Bool { return contains{ $0 <== .cacheMemoryOnly } } /// Whether only load the images from cache or not. public var onlyFromCache: Bool { return contains{ $0 <== .onlyFromCache } } /// Whether the image should be decoded in background or not. public var backgroundDecode: Bool { return contains{ $0 <== .backgroundDecode } } /// Whether the image data should be all loaded at once if it is an animated image. public var preloadAllAnimationData: Bool { return contains { $0 <== .preloadAllAnimationData } } /// The queue of callbacks should happen from Kingfisher. public var callbackDispatchQueue: DispatchQueue { if let item = lastMatchIgnoringAssociatedValue(.callbackDispatchQueue(nil)), case .callbackDispatchQueue(let queue) = item { return queue ?? DispatchQueue.main } return DispatchQueue.main } /// The scale factor which should be used for the image. public var scaleFactor: CGFloat { if let item = lastMatchIgnoringAssociatedValue(.scaleFactor(0)), case .scaleFactor(let scale) = item { return scale } return 1.0 } /// The `ImageDownloadRequestModifier` will be used before sending a download request. public var modifier: ImageDownloadRequestModifier { if let item = lastMatchIgnoringAssociatedValue(.requestModifier(NoModifier.default)), case .requestModifier(let modifier) = item { return modifier } return NoModifier.default } /// `ImageProcessor` for processing when the downloading finishes. public var processor: ImageProcessor { if let item = lastMatchIgnoringAssociatedValue(.processor(DefaultImageProcessor.default)), case .processor(let processor) = item { return processor } return DefaultImageProcessor.default } /// `ImageModifier` for modifying right before the image is displayed. public var imageModifier: ImageModifier { if let item = lastMatchIgnoringAssociatedValue(.imageModifier(DefaultImageModifier.default)), case .imageModifier(let imageModifier) = item { return imageModifier } return DefaultImageModifier.default } /// `CacheSerializer` to convert image to data for storing in cache. public var cacheSerializer: CacheSerializer { if let item = lastMatchIgnoringAssociatedValue(.cacheSerializer(DefaultCacheSerializer.default)), case .cacheSerializer(let cacheSerializer) = item { return cacheSerializer } return DefaultCacheSerializer.default } /// Keep the existing image while setting another image to an image view. /// Or the placeholder will be used while downloading. public var keepCurrentImageWhileLoading: Bool { return contains { $0 <== .keepCurrentImageWhileLoading } } public var onlyLoadFirstFrame: Bool { return contains { $0 <== .onlyLoadFirstFrame } } public var cacheOriginalImage: Bool { return contains { $0 <== .cacheOriginalImage } } }