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.
244 lines
10 KiB
244 lines
10 KiB
// The MIT License (MIT)
|
|
//
|
|
// Copyright (c) 2015-2021 Alexander Grebenyuk (github.com/kean).
|
|
|
|
import Foundation
|
|
import os
|
|
|
|
// MARK: - ImagePipeline.Configuration
|
|
|
|
extension ImagePipeline {
|
|
public struct Configuration {
|
|
// MARK: - Dependencies
|
|
|
|
/// Image cache used by the pipeline.
|
|
public var imageCache: ImageCaching? {
|
|
// This exists simply to ensure we don't init ImageCache.shared if the
|
|
// user provides their own instance.
|
|
get {
|
|
isCustomImageCacheProvided ? customImageCache : ImageCache.shared
|
|
}
|
|
set {
|
|
customImageCache = newValue
|
|
isCustomImageCacheProvided = true
|
|
}
|
|
}
|
|
private var customImageCache: ImageCaching?
|
|
private var isCustomImageCacheProvided = false
|
|
|
|
/// Data loader used by the pipeline.
|
|
public var dataLoader: DataLoading
|
|
|
|
/// Data cache used by the pipeline.
|
|
public var dataCache: DataCaching?
|
|
|
|
/// Default implementation uses shared `ImageDecoderRegistry` to create
|
|
/// a decoder that matches the context.
|
|
public var makeImageDecoder: (ImageDecodingContext) -> ImageDecoding? = ImageDecoderRegistry.shared.decoder(for:)
|
|
|
|
/// Returns `ImageEncoders.Default()` by default.
|
|
public var makeImageEncoder: (ImageEncodingContext) -> ImageEncoding = { _ in
|
|
ImageEncoders.Default()
|
|
}
|
|
|
|
// MARK: - Operation Queues
|
|
|
|
/// Data loading queue. Default maximum concurrent task count is 6.
|
|
public var dataLoadingQueue = OperationQueue()
|
|
|
|
/// Data caching queue. Default maximum concurrent task count is 2.
|
|
public var dataCachingQueue = OperationQueue()
|
|
|
|
/// Image decoding queue. Default maximum concurrent task count is 1.
|
|
public var imageDecodingQueue = OperationQueue()
|
|
|
|
/// Image encoding queue. Default maximum concurrent task count is 1.
|
|
public var imageEncodingQueue = OperationQueue()
|
|
|
|
/// Image processing queue. Default maximum concurrent task count is 2.
|
|
public var imageProcessingQueue = OperationQueue()
|
|
|
|
#if !os(macOS)
|
|
/// Image decompressing queue. Default maximum concurrent task count is 2.
|
|
public var imageDecompressingQueue = OperationQueue()
|
|
#endif
|
|
|
|
// MARK: - Processors
|
|
|
|
/// Processors to be applied by default to all images loaded by the
|
|
/// pipeline.
|
|
/// If a request has a non-empty processors list, the pipeline won't
|
|
/// apply its own processors, leaving the request as is.
|
|
/// This lets clients have an override point on request basis.
|
|
public var processors: [ImageProcessing] = []
|
|
|
|
// MARK: - Options
|
|
|
|
/// A queue on which all callbacks, like `progress` and `completion`
|
|
/// callbacks are called. `.main` by default.
|
|
public var callbackQueue = DispatchQueue.main
|
|
|
|
#if !os(macOS)
|
|
/// Decompresses the loaded images. `true` by default.
|
|
///
|
|
/// Decompressing compressed image formats (such as JPEG) can significantly
|
|
/// improve drawing performance as it allows a bitmap representation to be
|
|
/// created in a background rather than on the main thread.
|
|
public var isDecompressionEnabled = true
|
|
#endif
|
|
|
|
public var dataCacheOptions = DataCacheOptions()
|
|
|
|
public struct DataCacheOptions {
|
|
/// Specifies which content to store in the `dataCache`. By default, the
|
|
/// pipeline only stores the original image data downloaded using `dataLoader`.
|
|
/// It can be configured to encode and store processed images instead.
|
|
///
|
|
/// - note: If you are creating multiple versions of the same image using
|
|
/// different processors, it might be worth enabling both `.originalData`
|
|
/// and `.encodedImages` cache to reuse the same downloaded data.
|
|
///
|
|
/// - note: It might be worth enabling `.encodedImages` if you want to
|
|
/// transcode downloaded images into a more efficient format, like HEIF.
|
|
public var storedItems: Set<DataCacheItem> = [.originalImageData]
|
|
}
|
|
|
|
/// `true` by default. If `true` the pipeline avoids duplicated work when
|
|
/// loading images. The work only gets cancelled when all the registered
|
|
/// requests are. The pipeline also automatically manages the priority of the
|
|
/// deduplicated work.
|
|
///
|
|
/// Let's take these two requests for example:
|
|
///
|
|
/// ```swift
|
|
/// let url = URL(string: "http://example.com/image")
|
|
/// pipeline.loadImage(with: ImageRequest(url: url, processors: [
|
|
/// ImageProcessors.Resize(size: CGSize(width: 44, height: 44)),
|
|
/// ImageProcessors.GaussianBlur(radius: 8)
|
|
/// ]))
|
|
/// pipeline.loadImage(with: ImageRequest(url: url, processors: [
|
|
/// ImageProcessors.Resize(size: CGSize(width: 44, height: 44))
|
|
/// ]))
|
|
/// ```
|
|
///
|
|
/// Nuke will load the image data only once, resize the image once and
|
|
/// apply the blur also only once. There is no duplicated work done at
|
|
/// any stage.
|
|
public var isDeduplicationEnabled = true
|
|
|
|
/// `true` by default. If `true` the pipeline will rate limit requests
|
|
/// to prevent trashing of the underlying systems (e.g. `URLSession`).
|
|
/// The rate limiter only comes into play when the requests are started
|
|
/// and cancelled at a high rate (e.g. scrolling through a collection view).
|
|
public var isRateLimiterEnabled = true
|
|
|
|
/// `false` by default. If `true` the pipeline will try to produce a new
|
|
/// image each time it receives a new portion of data from data loader.
|
|
/// The decoder used by the image loading session determines whether
|
|
/// to produce a partial image or not. The default image decoder
|
|
/// (`ImageDecoder`) supports progressive JPEG decoding.
|
|
public var isProgressiveDecodingEnabled = false
|
|
|
|
/// `false` by default. If `true`, the pipeline will store all of the
|
|
/// progressively generated previews in the memory cache. All of the
|
|
/// previews have `isPreview` flag set to `true`.
|
|
public var isStoringPreviewsInMemoryCache = false
|
|
|
|
/// If the data task is terminated (either because of a failure or a
|
|
/// cancellation) and the image was partially loaded, the next load will
|
|
/// resume where it left off. Supports both validators (`ETag`,
|
|
/// `Last-Modified`). Resumable downloads are enabled by default.
|
|
public var isResumableDataEnabled = true
|
|
|
|
// MARK: - Options (Shared)
|
|
|
|
/// If `true` pipeline will detect GIFs and set `animatedImageData`
|
|
/// (`UIImage` property). It will also disable processing of such images,
|
|
/// and alter the way cache cost is calculated. However, this will not
|
|
/// enable actual animated image rendering. To do that take a look at
|
|
/// satellite projects (FLAnimatedImage and Gifu plugins for Nuke).
|
|
/// `false` by default (to preserve resources).
|
|
static var _isAnimatedImageDataEnabled = false
|
|
|
|
/// `false` by default. If `true`, enables `os_signpost` logging for
|
|
/// measuring performance. You can visually see all the performance
|
|
/// metrics in `os_signpost` Instrument. For more information see
|
|
/// https://developer.apple.com/documentation/os/logging and
|
|
/// https://developer.apple.com/videos/play/wwdc2018/405/.
|
|
public static var isSignpostLoggingEnabled = false {
|
|
didSet {
|
|
log = isSignpostLoggingEnabled ?
|
|
OSLog(subsystem: "com.github.kean.Nuke.ImagePipeline", category: "Image Loading") :
|
|
.disabled
|
|
}
|
|
}
|
|
|
|
static var isFastTrackDecodingEnabled = true
|
|
|
|
// MARK: - Initializer
|
|
|
|
public init(dataLoader: DataLoading = DataLoader()) {
|
|
self.dataLoader = dataLoader
|
|
|
|
self.dataLoadingQueue.maxConcurrentOperationCount = 6
|
|
self.dataCachingQueue.maxConcurrentOperationCount = 2
|
|
self.imageDecodingQueue.maxConcurrentOperationCount = 1
|
|
self.imageEncodingQueue.maxConcurrentOperationCount = 1
|
|
self.imageProcessingQueue.maxConcurrentOperationCount = 2
|
|
#if !os(macOS)
|
|
self.imageDecompressingQueue.maxConcurrentOperationCount = 2
|
|
#endif
|
|
}
|
|
|
|
/// Creates a default configuration.
|
|
/// - parameter dataLoader: `DataLoader()` by default.
|
|
/// - parameter imageCache: `ImageCache.shared` by default.
|
|
public init(dataLoader: DataLoading = DataLoader(), imageCache: ImageCaching?) {
|
|
self.init(dataLoader: dataLoader)
|
|
self.customImageCache = imageCache
|
|
self.isCustomImageCacheProvided = true
|
|
} // This init is going to be removed in the future
|
|
}
|
|
|
|
public enum DataCacheItem {
|
|
/// Original image data.
|
|
case originalImageData
|
|
/// Final image with all processors applied.
|
|
case finalImage
|
|
}
|
|
}
|
|
|
|
// MARK: - ImagePipelineObserving
|
|
|
|
public enum ImageTaskEvent {
|
|
case started
|
|
case cancelled
|
|
case priorityUpdated(priority: ImageRequest.Priority)
|
|
case intermediateResponseReceived(response: ImageResponse)
|
|
case progressUpdated(completedUnitCount: Int64, totalUnitCount: Int64)
|
|
case completed(result: Result<ImageResponse, ImagePipeline.Error>)
|
|
}
|
|
|
|
/// Allows you to tap into internal events of the image pipeline. Events are
|
|
/// delivered on the internal serial dispatch queue.
|
|
public protocol ImagePipelineObserving {
|
|
/// Delivers the events produced by the image tasks started via `loadImage` method.
|
|
func pipeline(_ pipeline: ImagePipeline, imageTask: ImageTask, didReceiveEvent event: ImageTaskEvent)
|
|
}
|
|
|
|
extension ImageTaskEvent {
|
|
init(_ event: Task<ImageResponse, ImagePipeline.Error>.Event) {
|
|
switch event {
|
|
case let .error(error):
|
|
self = .completed(result: .failure(error))
|
|
case let .value(response, isCompleted):
|
|
if isCompleted {
|
|
self = .completed(result: .success(response))
|
|
} else {
|
|
self = .intermediateResponseReceived(response: response)
|
|
}
|
|
case let .progress(progress):
|
|
self = .progressUpdated(completedUnitCount: progress.completed, totalUnitCount: progress.total)
|
|
}
|
|
}
|
|
}
|