update
This commit is contained in:
148
Pods/Kingfisher/Sources/General/ImageSource/AVAssetImageDataProvider.swift
generated
Normal file
148
Pods/Kingfisher/Sources/General/ImageSource/AVAssetImageDataProvider.swift
generated
Normal file
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// AVAssetImageDataProvider.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2020/08/09.
|
||||
//
|
||||
// Copyright (c) 2020 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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(watchOS)
|
||||
|
||||
import Foundation
|
||||
import AVKit
|
||||
|
||||
#if canImport(MobileCoreServices)
|
||||
import MobileCoreServices
|
||||
#else
|
||||
import CoreServices
|
||||
#endif
|
||||
|
||||
/// A data provider to provide thumbnail data from a given AVKit asset.
|
||||
public struct AVAssetImageDataProvider: ImageDataProvider {
|
||||
|
||||
/// The possible error might be caused by the `AVAssetImageDataProvider`.
|
||||
/// - userCancelled: The data provider process is cancelled.
|
||||
/// - invalidImage: The retrieved image is invalid.
|
||||
public enum AVAssetImageDataProviderError: Error {
|
||||
case userCancelled
|
||||
case invalidImage(_ image: CGImage?)
|
||||
}
|
||||
|
||||
/// The asset image generator bound to `self`.
|
||||
public let assetImageGenerator: AVAssetImageGenerator
|
||||
|
||||
/// The time at which the image should be generate in the asset.
|
||||
public let time: CMTime
|
||||
|
||||
private var internalKey: String {
|
||||
return (assetImageGenerator.asset as? AVURLAsset)?.url.absoluteString ?? UUID().uuidString
|
||||
}
|
||||
|
||||
/// The cache key used by `self`.
|
||||
public var cacheKey: String {
|
||||
return "\(internalKey)_\(time.seconds)"
|
||||
}
|
||||
|
||||
/// Creates an asset image data provider.
|
||||
/// - Parameters:
|
||||
/// - assetImageGenerator: The asset image generator controls data providing behaviors.
|
||||
/// - time: At which time in the asset the image should be generated.
|
||||
public init(assetImageGenerator: AVAssetImageGenerator, time: CMTime) {
|
||||
self.assetImageGenerator = assetImageGenerator
|
||||
self.time = time
|
||||
}
|
||||
|
||||
/// Creates an asset image data provider.
|
||||
/// - Parameters:
|
||||
/// - assetURL: The URL of asset for providing image data.
|
||||
/// - time: At which time in the asset the image should be generated.
|
||||
///
|
||||
/// This method uses `assetURL` to create an `AVAssetImageGenerator` object and calls
|
||||
/// the `init(assetImageGenerator:time:)` initializer.
|
||||
///
|
||||
public init(assetURL: URL, time: CMTime) {
|
||||
let asset = AVAsset(url: assetURL)
|
||||
let generator = AVAssetImageGenerator(asset: asset)
|
||||
generator.appliesPreferredTrackTransform = true
|
||||
self.init(assetImageGenerator: generator, time: time)
|
||||
}
|
||||
|
||||
/// Creates an asset image data provider.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - assetURL: The URL of asset for providing image data.
|
||||
/// - seconds: At which time in seconds in the asset the image should be generated.
|
||||
///
|
||||
/// This method uses `assetURL` to create an `AVAssetImageGenerator` object, uses `seconds` to create a `CMTime`,
|
||||
/// and calls the `init(assetImageGenerator:time:)` initializer.
|
||||
///
|
||||
public init(assetURL: URL, seconds: TimeInterval) {
|
||||
let time = CMTime(seconds: seconds, preferredTimescale: 600)
|
||||
self.init(assetURL: assetURL, time: time)
|
||||
}
|
||||
|
||||
public func data(handler: @escaping (Result<Data, Error>) -> Void) {
|
||||
assetImageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time)]) {
|
||||
(requestedTime, image, imageTime, result, error) in
|
||||
if let error = error {
|
||||
handler(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
if result == .cancelled {
|
||||
handler(.failure(AVAssetImageDataProviderError.userCancelled))
|
||||
return
|
||||
}
|
||||
|
||||
guard let cgImage = image, let data = cgImage.jpegData else {
|
||||
handler(.failure(AVAssetImageDataProviderError.invalidImage(image)))
|
||||
return
|
||||
}
|
||||
|
||||
handler(.success(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CGImage {
|
||||
var jpegData: Data? {
|
||||
guard let mutableData = CFDataCreateMutable(nil, 0) else {
|
||||
return nil
|
||||
}
|
||||
#if os(xrOS)
|
||||
guard let destination = CGImageDestinationCreateWithData(
|
||||
mutableData, UTType.jpeg.identifier as CFString , 1, nil
|
||||
) else {
|
||||
return nil
|
||||
}
|
||||
#else
|
||||
guard let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil) else {
|
||||
return nil
|
||||
}
|
||||
#endif
|
||||
|
||||
CGImageDestinationAddImage(destination, self, nil)
|
||||
guard CGImageDestinationFinalize(destination) else { return nil }
|
||||
return mutableData as Data
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
190
Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift
generated
Normal file
190
Pods/Kingfisher/Sources/General/ImageSource/ImageDataProvider.swift
generated
Normal file
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// ImageDataProvider.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2018/11/13.
|
||||
//
|
||||
// Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents a data provider to provide image data to Kingfisher when setting with
|
||||
/// `Source.provider` source. Compared to `Source.network` member, it gives a chance
|
||||
/// to load some image data in your own way, as long as you can provide the data
|
||||
/// representation for the image.
|
||||
public protocol ImageDataProvider {
|
||||
|
||||
/// The key used in cache.
|
||||
var cacheKey: String { get }
|
||||
|
||||
/// Provides the data which represents image. Kingfisher uses the data you pass in the
|
||||
/// handler to process images and caches it for later use.
|
||||
///
|
||||
/// - Parameter handler: The handler you should call when you prepared your data.
|
||||
/// If the data is loaded successfully, call the handler with
|
||||
/// a `.success` with the data associated. Otherwise, call it
|
||||
/// with a `.failure` and pass the error.
|
||||
///
|
||||
/// - Note:
|
||||
/// If the `handler` is called with a `.failure` with error, a `dataProviderError` of
|
||||
/// `ImageSettingErrorReason` will be finally thrown out to you as the `KingfisherError`
|
||||
/// from the framework.
|
||||
func data(handler: @escaping (Result<Data, Error>) -> Void)
|
||||
|
||||
/// The content URL represents this provider, if exists.
|
||||
var contentURL: URL? { get }
|
||||
}
|
||||
|
||||
public extension ImageDataProvider {
|
||||
var contentURL: URL? { return nil }
|
||||
func convertToSource() -> Source {
|
||||
.provider(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an image data provider for loading from a local file URL on disk.
|
||||
/// Uses this type for adding a disk image to Kingfisher. Compared to loading it
|
||||
/// directly, you can get benefit of using Kingfisher's extension methods, as well
|
||||
/// as applying `ImageProcessor`s and storing the image to `ImageCache` of Kingfisher.
|
||||
public struct LocalFileImageDataProvider: ImageDataProvider {
|
||||
|
||||
// MARK: Public Properties
|
||||
|
||||
/// The file URL from which the image be loaded.
|
||||
public let fileURL: URL
|
||||
private let loadingQueue: ExecutionQueue
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates an image data provider by supplying the target local file URL.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - fileURL: The file URL from which the image be loaded.
|
||||
/// - cacheKey: The key is used for caching the image data. By default,
|
||||
/// the `absoluteString` of `fileURL` is used.
|
||||
/// - loadingQueue: The queue where the file loading should happen. By default, the dispatch queue of
|
||||
/// `.global(qos: .userInitiated)` will be used.
|
||||
public init(
|
||||
fileURL: URL,
|
||||
cacheKey: String? = nil,
|
||||
loadingQueue: ExecutionQueue = .dispatch(DispatchQueue.global(qos: .userInitiated))
|
||||
) {
|
||||
self.fileURL = fileURL
|
||||
self.cacheKey = cacheKey ?? fileURL.localFileCacheKey
|
||||
self.loadingQueue = loadingQueue
|
||||
}
|
||||
|
||||
// MARK: Protocol Conforming
|
||||
|
||||
/// The key used in cache.
|
||||
public var cacheKey: String
|
||||
|
||||
public func data(handler:@escaping (Result<Data, Error>) -> Void) {
|
||||
loadingQueue.execute {
|
||||
handler(Result(catching: { try Data(contentsOf: fileURL) }))
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.5)
|
||||
#if canImport(_Concurrency)
|
||||
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
public var data: Data {
|
||||
get async throws {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
loadingQueue.execute {
|
||||
do {
|
||||
let data = try Data(contentsOf: fileURL)
|
||||
continuation.resume(returning: data)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// The URL of the local file on the disk.
|
||||
public var contentURL: URL? {
|
||||
return fileURL
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an image data provider for loading image from a given Base64 encoded string.
|
||||
public struct Base64ImageDataProvider: ImageDataProvider {
|
||||
|
||||
// MARK: Public Properties
|
||||
/// The encoded Base64 string for the image.
|
||||
public let base64String: String
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates an image data provider by supplying the Base64 encoded string.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - base64String: The Base64 encoded string for an image.
|
||||
/// - cacheKey: The key is used for caching the image data. You need a different key for any different image.
|
||||
public init(base64String: String, cacheKey: String) {
|
||||
self.base64String = base64String
|
||||
self.cacheKey = cacheKey
|
||||
}
|
||||
|
||||
// MARK: Protocol Conforming
|
||||
|
||||
/// The key used in cache.
|
||||
public var cacheKey: String
|
||||
|
||||
public func data(handler: (Result<Data, Error>) -> Void) {
|
||||
let data = Data(base64Encoded: base64String)!
|
||||
handler(.success(data))
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an image data provider for a raw data object.
|
||||
public struct RawImageDataProvider: ImageDataProvider {
|
||||
|
||||
// MARK: Public Properties
|
||||
|
||||
/// The raw data object to provide to Kingfisher image loader.
|
||||
public let data: Data
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates an image data provider by the given raw `data` value and a `cacheKey` be used in Kingfisher cache.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - data: The raw data reprensents an image.
|
||||
/// - cacheKey: The key is used for caching the image data. You need a different key for any different image.
|
||||
public init(data: Data, cacheKey: String) {
|
||||
self.data = data
|
||||
self.cacheKey = cacheKey
|
||||
}
|
||||
|
||||
// MARK: Protocol Conforming
|
||||
|
||||
/// The key used in cache.
|
||||
public var cacheKey: String
|
||||
|
||||
public func data(handler: @escaping (Result<Data, Error>) -> Void) {
|
||||
handler(.success(data))
|
||||
}
|
||||
}
|
||||
121
Pods/Kingfisher/Sources/General/ImageSource/Resource.swift
generated
Normal file
121
Pods/Kingfisher/Sources/General/ImageSource/Resource.swift
generated
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Resource.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 15/4/6.
|
||||
//
|
||||
// Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents an image resource at a certain url and a given cache key.
|
||||
/// Kingfisher will use a `Resource` to download a resource from network and cache it with the cache key when
|
||||
/// using `Source.network` as its image setting source.
|
||||
public protocol Resource {
|
||||
|
||||
/// The key used in cache.
|
||||
var cacheKey: String { get }
|
||||
|
||||
/// The target image URL.
|
||||
var downloadURL: URL { get }
|
||||
}
|
||||
|
||||
extension Resource {
|
||||
|
||||
/// Converts `self` to a valid `Source` based on its `downloadURL` scheme. A `.provider` with
|
||||
/// `LocalFileImageDataProvider` associated will be returned if the URL points to a local file. Otherwise,
|
||||
/// `.network` is returned.
|
||||
public func convertToSource(overrideCacheKey: String? = nil) -> Source {
|
||||
let key = overrideCacheKey ?? cacheKey
|
||||
return downloadURL.isFileURL ?
|
||||
.provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: key)) :
|
||||
.network(KF.ImageResource(downloadURL: downloadURL, cacheKey: key))
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "This type conflicts with `GeneratedAssetSymbols.ImageResource` in Swift 5.9. Renamed to avoid issues in the future.", renamed: "KF.ImageResource")
|
||||
public typealias ImageResource = KF.ImageResource
|
||||
|
||||
|
||||
extension KF {
|
||||
/// ImageResource is a simple combination of `downloadURL` and `cacheKey`.
|
||||
/// When passed to image view set methods, Kingfisher will try to download the target
|
||||
/// image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache.
|
||||
public struct ImageResource: Resource {
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
/// Creates an image resource.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloadURL: The target image URL from where the image can be downloaded.
|
||||
/// - cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key.
|
||||
/// Default is `nil`.
|
||||
public init(downloadURL: URL, cacheKey: String? = nil) {
|
||||
self.downloadURL = downloadURL
|
||||
self.cacheKey = cacheKey ?? downloadURL.cacheKey
|
||||
}
|
||||
|
||||
// MARK: Protocol Conforming
|
||||
|
||||
/// The key used in cache.
|
||||
public let cacheKey: String
|
||||
|
||||
/// The target image URL.
|
||||
public let downloadURL: URL
|
||||
}
|
||||
}
|
||||
|
||||
/// URL conforms to `Resource` in Kingfisher.
|
||||
/// The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`.
|
||||
/// If you need customize the url and/or cache key, use `ImageResource` instead.
|
||||
extension URL: Resource {
|
||||
public var cacheKey: String { return isFileURL ? localFileCacheKey : absoluteString }
|
||||
public var downloadURL: URL { return self }
|
||||
}
|
||||
|
||||
extension URL {
|
||||
static let localFileCacheKeyPrefix = "kingfisher.local.cacheKey"
|
||||
|
||||
// The special version of cache key for a local file on disk. Every time the app is reinstalled on the disk,
|
||||
// the system assigns a new container folder to hold the .app (and the extensions, .appex) folder. So the URL for
|
||||
// the same image in bundle might be different.
|
||||
//
|
||||
// This getter only uses the fixed part in the URL (until the bundle name folder) to provide a stable cache key
|
||||
// for the image under the same path inside the bundle.
|
||||
//
|
||||
// See #1825 (https://github.com/onevcat/Kingfisher/issues/1825)
|
||||
var localFileCacheKey: String {
|
||||
var validComponents: [String] = []
|
||||
for part in pathComponents.reversed() {
|
||||
validComponents.append(part)
|
||||
if part.hasSuffix(".app") || part.hasSuffix(".appex") {
|
||||
break
|
||||
}
|
||||
}
|
||||
let fixedPath = "\(Self.localFileCacheKeyPrefix)/\(validComponents.reversed().joined(separator: "/"))"
|
||||
if let q = query {
|
||||
return "\(fixedPath)?\(q)"
|
||||
} else {
|
||||
return fixedPath
|
||||
}
|
||||
}
|
||||
}
|
||||
116
Pods/Kingfisher/Sources/General/ImageSource/Source.swift
generated
Normal file
116
Pods/Kingfisher/Sources/General/ImageSource/Source.swift
generated
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Source.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2018/11/17.
|
||||
//
|
||||
// Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents an image setting source for Kingfisher methods.
|
||||
///
|
||||
/// A `Source` value indicates the way how the target image can be retrieved and cached.
|
||||
///
|
||||
/// - network: The target image should be got from network remotely. The associated `Resource`
|
||||
/// value defines detail information like image URL and cache key.
|
||||
/// - provider: The target image should be provided in a data format. Normally, it can be an image
|
||||
/// from local storage or in any other encoding format (like Base64).
|
||||
public enum Source {
|
||||
|
||||
/// Represents the source task identifier when setting an image to a view with extension methods.
|
||||
public enum Identifier {
|
||||
|
||||
/// The underlying value type of source identifier.
|
||||
public typealias Value = UInt
|
||||
static private(set) var current: Value = 0
|
||||
static func next() -> Value {
|
||||
current += 1
|
||||
return current
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Member Cases
|
||||
|
||||
/// The target image should be got from network remotely. The associated `Resource`
|
||||
/// value defines detail information like image URL and cache key.
|
||||
case network(Resource)
|
||||
|
||||
/// The target image should be provided in a data format. Normally, it can be an image
|
||||
/// from local storage or in any other encoding format (like Base64).
|
||||
case provider(ImageDataProvider)
|
||||
|
||||
// MARK: Getting Properties
|
||||
|
||||
/// The cache key defined for this source value.
|
||||
public var cacheKey: String {
|
||||
switch self {
|
||||
case .network(let resource): return resource.cacheKey
|
||||
case .provider(let provider): return provider.cacheKey
|
||||
}
|
||||
}
|
||||
|
||||
/// The URL defined for this source value.
|
||||
///
|
||||
/// For a `.network` source, it is the `downloadURL` of associated `Resource` instance.
|
||||
/// For a `.provider` value, it is always `nil`.
|
||||
public var url: URL? {
|
||||
switch self {
|
||||
case .network(let resource): return resource.downloadURL
|
||||
case .provider(let provider): return provider.contentURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Source: Hashable {
|
||||
public static func == (lhs: Source, rhs: Source) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.network(let r1), .network(let r2)):
|
||||
return r1.cacheKey == r2.cacheKey && r1.downloadURL == r2.downloadURL
|
||||
case (.provider(let p1), .provider(let p2)):
|
||||
return p1.cacheKey == p2.cacheKey && p1.contentURL == p2.contentURL
|
||||
case (.provider(_), .network(_)):
|
||||
return false
|
||||
case (.network(_), .provider(_)):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .network(let r):
|
||||
hasher.combine(r.cacheKey)
|
||||
hasher.combine(r.downloadURL)
|
||||
case .provider(let p):
|
||||
hasher.combine(p.cacheKey)
|
||||
hasher.combine(p.contentURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Source {
|
||||
var asResource: Resource? {
|
||||
guard case .network(let resource) = self else {
|
||||
return nil
|
||||
}
|
||||
return resource
|
||||
}
|
||||
}
|
||||
442
Pods/Kingfisher/Sources/General/KF.swift
generated
Normal file
442
Pods/Kingfisher/Sources/General/KF.swift
generated
Normal file
@@ -0,0 +1,442 @@
|
||||
//
|
||||
// KF.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2020/09/21.
|
||||
//
|
||||
// Copyright (c) 2020 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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 canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
#if canImport(CarPlay) && !targetEnvironment(macCatalyst)
|
||||
import CarPlay
|
||||
#endif
|
||||
|
||||
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
#if canImport(WatchKit)
|
||||
import WatchKit
|
||||
#endif
|
||||
|
||||
#if canImport(TVUIKit)
|
||||
import TVUIKit
|
||||
#endif
|
||||
|
||||
/// A helper type to create image setting tasks in a builder pattern.
|
||||
/// Use methods in this type to create a `KF.Builder` instance and configure image tasks there.
|
||||
public enum KF {
|
||||
|
||||
/// Creates a builder for a given `Source`.
|
||||
/// - Parameter source: The `Source` object defines data information from network or a data provider.
|
||||
/// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
|
||||
/// to start the image loading.
|
||||
public static func source(_ source: Source?) -> KF.Builder {
|
||||
Builder(source: source)
|
||||
}
|
||||
|
||||
/// Creates a builder for a given `Resource`.
|
||||
/// - Parameter resource: The `Resource` object defines data information like key or URL.
|
||||
/// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
|
||||
/// to start the image loading.
|
||||
public static func resource(_ resource: Resource?) -> KF.Builder {
|
||||
source(resource?.convertToSource())
|
||||
}
|
||||
|
||||
/// Creates a builder for a given `URL` and an optional cache key.
|
||||
/// - Parameters:
|
||||
/// - url: The URL where the image should be downloaded.
|
||||
/// - cacheKey: The key used to store the downloaded image in cache.
|
||||
/// If `nil`, the `absoluteString` of `url` is used as the cache key.
|
||||
/// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
|
||||
/// to start the image loading.
|
||||
public static func url(_ url: URL?, cacheKey: String? = nil) -> KF.Builder {
|
||||
source(url?.convertToSource(overrideCacheKey: cacheKey))
|
||||
}
|
||||
|
||||
/// Creates a builder for a given `ImageDataProvider`.
|
||||
/// - Parameter provider: The `ImageDataProvider` object contains information about the data.
|
||||
/// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
|
||||
/// to start the image loading.
|
||||
public static func dataProvider(_ provider: ImageDataProvider?) -> KF.Builder {
|
||||
source(provider?.convertToSource())
|
||||
}
|
||||
|
||||
/// Creates a builder for some given raw data and a cache key.
|
||||
/// - Parameters:
|
||||
/// - data: The data object from which the image should be created.
|
||||
/// - cacheKey: The key used to store the downloaded image in cache.
|
||||
/// - Returns: A `KF.Builder` for future configuration. After configuring the builder, call `set(to:)`
|
||||
/// to start the image loading.
|
||||
public static func data(_ data: Data?, cacheKey: String) -> KF.Builder {
|
||||
if let data = data {
|
||||
return dataProvider(RawImageDataProvider(data: data, cacheKey: cacheKey))
|
||||
} else {
|
||||
return dataProvider(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension KF {
|
||||
|
||||
/// A builder class to configure an image retrieving task and set it to a holder view or component.
|
||||
public class Builder {
|
||||
private let source: Source?
|
||||
|
||||
#if os(watchOS)
|
||||
private var placeholder: KFCrossPlatformImage?
|
||||
#else
|
||||
private var placeholder: Placeholder?
|
||||
#endif
|
||||
|
||||
public var options = KingfisherParsedOptionsInfo(KingfisherManager.shared.defaultOptions)
|
||||
|
||||
public let onFailureDelegate = Delegate<KingfisherError, Void>()
|
||||
public let onSuccessDelegate = Delegate<RetrieveImageResult, Void>()
|
||||
public let onProgressDelegate = Delegate<(Int64, Int64), Void>()
|
||||
|
||||
init(source: Source?) {
|
||||
self.source = source
|
||||
}
|
||||
|
||||
private var resultHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? {
|
||||
{
|
||||
switch $0 {
|
||||
case .success(let result):
|
||||
self.onSuccessDelegate(result)
|
||||
case .failure(let error):
|
||||
self.onFailureDelegate(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var progressBlock: DownloadProgressBlock {
|
||||
{ self.onProgressDelegate(($0, $1)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KF.Builder {
|
||||
#if !os(watchOS)
|
||||
|
||||
/// Builds the image task request and sets it to an image view.
|
||||
/// - Parameter imageView: The image view which loads the task and should be set with the image.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@discardableResult
|
||||
public func set(to imageView: KFCrossPlatformImageView) -> DownloadTask? {
|
||||
imageView.kf.setImage(
|
||||
with: source,
|
||||
placeholder: placeholder,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds the image task request and sets it to an `NSTextAttachment` object.
|
||||
/// - Parameters:
|
||||
/// - attachment: The text attachment object which loads the task and should be set with the image.
|
||||
/// - attributedView: The owner of the attributed string which this `NSTextAttachment` is added.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@discardableResult
|
||||
public func set(to attachment: NSTextAttachment, attributedView: @autoclosure @escaping () -> KFCrossPlatformView) -> DownloadTask? {
|
||||
let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
|
||||
return attachment.kf.setImage(
|
||||
with: source,
|
||||
attributedView: attributedView,
|
||||
placeholder: placeholderImage,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
|
||||
/// Builds the image task request and sets it to a button.
|
||||
/// - Parameters:
|
||||
/// - button: The button which loads the task and should be set with the image.
|
||||
/// - state: The button state to which the image should be set.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@discardableResult
|
||||
public func set(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
|
||||
let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
|
||||
return button.kf.setImage(
|
||||
with: source,
|
||||
for: state,
|
||||
placeholder: placeholderImage,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds the image task request and sets it to the background image for a button.
|
||||
/// - Parameters:
|
||||
/// - button: The button which loads the task and should be set with the image.
|
||||
/// - state: The button state to which the image should be set.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@discardableResult
|
||||
public func setBackground(to button: UIButton, for state: UIControl.State) -> DownloadTask? {
|
||||
let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
|
||||
return button.kf.setBackgroundImage(
|
||||
with: source,
|
||||
for: state,
|
||||
placeholder: placeholderImage,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
#endif // end of canImport(UIKit)
|
||||
|
||||
#if canImport(CarPlay) && !targetEnvironment(macCatalyst)
|
||||
|
||||
/// Builds the image task request and sets it to the image for a list item.
|
||||
/// - Parameters:
|
||||
/// - listItem: The list item which loads the task and should be set with the image.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@available(iOS 14.0, *)
|
||||
@discardableResult
|
||||
public func set(to listItem: CPListItem) -> DownloadTask? {
|
||||
let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
|
||||
return listItem.kf.setImage(
|
||||
with: source,
|
||||
placeholder: placeholderImage,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
|
||||
/// Builds the image task request and sets it to a button.
|
||||
/// - Parameter button: The button which loads the task and should be set with the image.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@discardableResult
|
||||
public func set(to button: NSButton) -> DownloadTask? {
|
||||
let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
|
||||
return button.kf.setImage(
|
||||
with: source,
|
||||
placeholder: placeholderImage,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds the image task request and sets it to the alternative image for a button.
|
||||
/// - Parameter button: The button which loads the task and should be set with the image.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@discardableResult
|
||||
public func setAlternative(to button: NSButton) -> DownloadTask? {
|
||||
let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
|
||||
return button.kf.setAlternateImage(
|
||||
with: source,
|
||||
placeholder: placeholderImage,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
#endif // end of canImport(AppKit)
|
||||
#endif // end of !os(watchOS)
|
||||
|
||||
#if canImport(WatchKit)
|
||||
/// Builds the image task request and sets it to a `WKInterfaceImage` object.
|
||||
/// - Parameter interfaceImage: The watch interface image which loads the task and should be set with the image.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@discardableResult
|
||||
public func set(to interfaceImage: WKInterfaceImage) -> DownloadTask? {
|
||||
return interfaceImage.kf.setImage(
|
||||
with: source,
|
||||
placeholder: placeholder,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
#endif // end of canImport(WatchKit)
|
||||
|
||||
#if canImport(TVUIKit)
|
||||
/// Builds the image task request and sets it to a TV monogram view.
|
||||
/// - Parameter monogramView: The monogram view which loads the task and should be set with the image.
|
||||
/// - Returns: A task represents the image downloading, if initialized.
|
||||
/// This value is `nil` if the image is being loaded from cache.
|
||||
@available(tvOS 12.0, *)
|
||||
@discardableResult
|
||||
public func set(to monogramView: TVMonogramView) -> DownloadTask? {
|
||||
let placeholderImage = placeholder as? KFCrossPlatformImage ?? nil
|
||||
return monogramView.kf.setImage(
|
||||
with: source,
|
||||
placeholder: placeholderImage,
|
||||
parsedOptions: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: resultHandler
|
||||
)
|
||||
}
|
||||
#endif // end of canImport(TVUIKit)
|
||||
}
|
||||
|
||||
#if !os(watchOS)
|
||||
extension KF.Builder {
|
||||
#if os(iOS) || os(tvOS)
|
||||
|
||||
/// Sets a placeholder which is used while retrieving the image.
|
||||
/// - Parameter placeholder: A placeholder to show while retrieving the image from its source.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
public func placeholder(_ placeholder: Placeholder?) -> Self {
|
||||
self.placeholder = placeholder
|
||||
return self
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Sets a placeholder image which is used while retrieving the image.
|
||||
/// - Parameter placeholder: An image to show while retrieving the image from its source.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
public func placeholder(_ image: KFCrossPlatformImage?) -> Self {
|
||||
self.placeholder = image
|
||||
return self
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extension KF.Builder {
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
/// Sets the transition for the image task.
|
||||
/// - Parameter transition: The desired transition effect when setting the image to image view.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
///
|
||||
/// Kingfisher will use the `transition` 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, also call `forceRefresh()` on the returned `KF.Builder`.
|
||||
public func transition(_ transition: ImageTransition) -> Self {
|
||||
options.transition = transition
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets a fade transition for the image task.
|
||||
/// - Parameter duration: The duration of the fade transition.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
///
|
||||
/// Kingfisher will use the fade transition 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, also call `forceRefresh()` on the returned `KF.Builder`.
|
||||
public func fade(duration: TimeInterval) -> Self {
|
||||
options.transition = .fade(duration)
|
||||
return self
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Sets whether keeping the existing image of image view while setting another image to it.
|
||||
/// - Parameter enabled: Whether the existing image should be kept.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
///
|
||||
/// By setting this option, the placeholder image parameter of image view extension method
|
||||
/// will be ignored and the current image will be kept while loading or downloading the new image.
|
||||
///
|
||||
public func keepCurrentImageWhileLoading(_ enabled: Bool = true) -> Self {
|
||||
options.keepCurrentImageWhileLoading = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether only the first frame from an animated image file should be loaded as a single image.
|
||||
/// - Parameter enabled: Whether the only the first frame should be loaded.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
///
|
||||
/// Loading an animated images may take too much memory. It will be useful when you want to display a
|
||||
/// static preview of the first frame from an animated image.
|
||||
///
|
||||
/// This option will be ignored if the target image is not animated image data.
|
||||
///
|
||||
public func onlyLoadFirstFrame(_ enabled: Bool = true) -> Self {
|
||||
options.onlyLoadFirstFrame = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Enables progressive image loading with a specified `ImageProgressive` setting to process the
|
||||
/// progressive JPEG data and display it in a progressive way.
|
||||
/// - Parameter progressive: The progressive settings which is used while loading.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
public func progressiveJPEG(_ progressive: ImageProgressive? = .init()) -> Self {
|
||||
options.progressiveJPEG = progressive
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Deprecated
|
||||
extension KF.Builder {
|
||||
/// Starts the loading process of `self` immediately.
|
||||
///
|
||||
/// By default, a `KFImage` will not load its source until the `onAppear` is called. This is a lazily loading
|
||||
/// behavior and provides better performance. However, when you refresh the view, the lazy loading also causes a
|
||||
/// flickering since the loading does not happen immediately. Call this method if you want to start the load at once
|
||||
/// could help avoiding the flickering, with some performance trade-off.
|
||||
///
|
||||
/// - Deprecated: This is not necessary anymore since `@StateObject` is used for holding the image data.
|
||||
/// It does nothing now and please just remove it.
|
||||
///
|
||||
/// - Returns: The `Self` value with changes applied.
|
||||
@available(*, deprecated, message: "This is not necessary anymore since `@StateObject` is used. It does nothing now and please just remove it.")
|
||||
public func loadImmediately(_ start: Bool = true) -> Self {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Redirect Handler
|
||||
extension KF {
|
||||
|
||||
/// Represents the detail information when a task redirect happens. It is wrapping necessary information for a
|
||||
/// `ImageDownloadRedirectHandler`. See that protocol for more information.
|
||||
public struct RedirectPayload {
|
||||
|
||||
/// The related session data task when the redirect happens. It is
|
||||
/// the current `SessionDataTask` which triggers this redirect.
|
||||
public let task: SessionDataTask
|
||||
|
||||
/// The response received during redirection.
|
||||
public let response: HTTPURLResponse
|
||||
|
||||
/// The request for redirection which can be modified.
|
||||
public let newRequest: URLRequest
|
||||
|
||||
/// A closure for being called with modified request.
|
||||
public let completionHandler: (URLRequest?) -> Void
|
||||
}
|
||||
}
|
||||
706
Pods/Kingfisher/Sources/General/KFOptionsSetter.swift
generated
Normal file
706
Pods/Kingfisher/Sources/General/KFOptionsSetter.swift
generated
Normal file
@@ -0,0 +1,706 @@
|
||||
//
|
||||
// KFOptionsSetter.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2020/12/22.
|
||||
//
|
||||
// Copyright (c) 2020 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
public protocol KFOptionSetter {
|
||||
var options: KingfisherParsedOptionsInfo { get nonmutating set }
|
||||
|
||||
var onFailureDelegate: Delegate<KingfisherError, Void> { get }
|
||||
var onSuccessDelegate: Delegate<RetrieveImageResult, Void> { get }
|
||||
var onProgressDelegate: Delegate<(Int64, Int64), Void> { get }
|
||||
|
||||
var delegateObserver: AnyObject { get }
|
||||
}
|
||||
|
||||
extension KF.Builder: KFOptionSetter {
|
||||
public var delegateObserver: AnyObject { self }
|
||||
}
|
||||
|
||||
// MARK: - Life cycles
|
||||
extension KFOptionSetter {
|
||||
/// Sets the progress block to current builder.
|
||||
/// - Parameter block: Called when the image downloading progress gets updated. If the response does not contain an
|
||||
/// `expectedContentLength`, this block will not be called. If `block` is `nil`, the callback
|
||||
/// will be reset.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func onProgress(_ block: DownloadProgressBlock?) -> Self {
|
||||
onProgressDelegate.delegate(on: delegateObserver) { (observer, result) in
|
||||
block?(result.0, result.1)
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the the done block to current builder.
|
||||
/// - Parameter block: Called when the image task successfully completes and the the image set is done. If `block`
|
||||
/// is `nil`, the callback will be reset.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
public func onSuccess(_ block: ((RetrieveImageResult) -> Void)?) -> Self {
|
||||
onSuccessDelegate.delegate(on: delegateObserver) { (observer, result) in
|
||||
block?(result)
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the catch block to current builder.
|
||||
/// - Parameter block: Called when an error happens during the image task. If `block`
|
||||
/// is `nil`, the callback will be reset.
|
||||
/// - Returns: A `KF.Builder` with changes applied.
|
||||
public func onFailure(_ block: ((KingfisherError) -> Void)?) -> Self {
|
||||
onFailureDelegate.delegate(on: delegateObserver) { (observer, error) in
|
||||
block?(error)
|
||||
}
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Basic options settings.
|
||||
extension KFOptionSetter {
|
||||
/// Sets the target image cache for this task.
|
||||
/// - Parameter cache: The target cache is about to be used for the task.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// Kingfisher will use the associated `ImageCache` object when handling related operations,
|
||||
/// including trying to retrieve the cached images and store the downloaded image to it.
|
||||
///
|
||||
public func targetCache(_ cache: ImageCache) -> Self {
|
||||
options.targetCache = cache
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the target image cache to store the original downloaded image for this task.
|
||||
/// - Parameter cache: The target cache is about to be used for storing the original downloaded image from the task.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// The `ImageCache` for storing and retrieving original images. If `originalCache` is
|
||||
/// contained in the options, it will be preferred for storing and retrieving original images.
|
||||
/// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images.
|
||||
///
|
||||
/// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is
|
||||
/// applied in the option, the original image will be stored to this `originalCache`. At the
|
||||
/// same time, if a requested final image (with processor applied) cannot be found in `targetCache`,
|
||||
/// Kingfisher will try to search the original image to check whether it is already there. If found,
|
||||
/// it will be used and applied with the given processor. It is an optimization for not downloading
|
||||
/// the same image for multiple times.
|
||||
///
|
||||
public func originalCache(_ cache: ImageCache) -> Self {
|
||||
options.originalCache = cache
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the downloader used to perform the image download task.
|
||||
/// - Parameter downloader: The downloader which is about to be used for downloading.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// Kingfisher will use the set `ImageDownloader` object to download the requested images.
|
||||
public func downloader(_ downloader: ImageDownloader) -> Self {
|
||||
options.downloader = downloader
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the download priority for the image task.
|
||||
/// - Parameter priority: The download priority of image download task.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// The `priority` value will be set as the priority of the image download task. The value for it should be
|
||||
/// between 0.0~1.0. You can choose a value between `URLSessionTask.defaultPriority`, `URLSessionTask.lowPriority`
|
||||
/// or `URLSessionTask.highPriority`. If this option not set, the default value (`URLSessionTask.defaultPriority`)
|
||||
/// will be used.
|
||||
public func downloadPriority(_ priority: Float) -> Self {
|
||||
options.downloadPriority = priority
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether Kingfisher should ignore the cache and try to start a download task for the image source.
|
||||
/// - Parameter enabled: Enable the force refresh or not.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func forceRefresh(_ enabled: Bool = true) -> Self {
|
||||
options.forceRefresh = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether Kingfisher should try to retrieve the image from memory cache first. If not found, it ignores the
|
||||
/// disk cache and starts a download task for the image source.
|
||||
/// - Parameter enabled: Enable the memory-only cache searching or not.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// This is useful when you want to display a changeable image behind the same url at the same app session, while
|
||||
/// avoiding download it for multiple times.
|
||||
public func fromMemoryCacheOrRefresh(_ enabled: Bool = true) -> Self {
|
||||
options.fromMemoryCacheOrRefresh = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether the image should only be cached in memory but not in disk.
|
||||
/// - Parameter enabled: Whether the image should be only cache in memory or not.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func cacheMemoryOnly(_ enabled: Bool = true) -> Self {
|
||||
options.cacheMemoryOnly = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether Kingfisher should wait for caching operation to be completed before calling the
|
||||
/// `onSuccess` or `onFailure` block.
|
||||
/// - Parameter enabled: Whether Kingfisher should wait for caching operation.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func waitForCache(_ enabled: Bool = true) -> Self {
|
||||
options.waitForCache = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether Kingfisher should only try to retrieve the image from cache, but not from network.
|
||||
/// - Parameter enabled: Whether Kingfisher should only try to retrieve the image from cache.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// If the image is not in cache, the image retrieving will fail with the
|
||||
/// `KingfisherError.cacheError` with `.imageNotExisting` as its reason.
|
||||
public func onlyFromCache(_ enabled: Bool = true) -> Self {
|
||||
options.onlyFromCache = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether the image should be decoded in a background thread before using.
|
||||
/// - Parameter enabled: Whether the image should be decoded in a background thread before using.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// Setting to `true` will decode the downloaded image data and do a off-screen rendering to extract pixel
|
||||
/// information in background. This can speed up display, but will cost more time and memory to prepare the image
|
||||
/// for using.
|
||||
public func backgroundDecode(_ enabled: Bool = true) -> Self {
|
||||
options.backgroundDecode = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the callback queue which is used as the target queue of dispatch callbacks when retrieving images from
|
||||
/// cache. If not set, Kingfisher will use main queue for callbacks.
|
||||
/// - Parameter queue: The target queue which the cache retrieving callback will be invoked on.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// - Note:
|
||||
/// This option does not affect the callbacks for UI related extension methods or `KFImage` result handlers.
|
||||
/// You will always get the callbacks called from main queue.
|
||||
public func callbackQueue(_ queue: CallbackQueue) -> Self {
|
||||
options.callbackQueue = queue
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the scale factor value when converting retrieved data to an image.
|
||||
/// - Parameter factor: The scale factor value.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing
|
||||
/// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0.
|
||||
///
|
||||
public func scaleFactor(_ factor: CGFloat) -> Self {
|
||||
options.scaleFactor = factor
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether the original image should be cached even when the original image has been processed by any other
|
||||
/// `ImageProcessor`s.
|
||||
/// - Parameter enabled: Whether the original image should be cached.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// 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
|
||||
/// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original
|
||||
/// images if necessary.
|
||||
///
|
||||
/// The original image will be only cached to disk storage.
|
||||
///
|
||||
public func cacheOriginalImage(_ enabled: Bool = true) -> Self {
|
||||
options.cacheOriginalImage = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets writing options for an original image on a first write
|
||||
/// - Parameter writingOptions: Options to control the writing of data to a disk storage.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
/// If set, options will be passed the store operation for a new files.
|
||||
///
|
||||
/// This is useful if you want to implement file enctyption on first write - eg [.completeFileProtection]
|
||||
///
|
||||
public func diskStoreWriteOptions(_ writingOptions: Data.WritingOptions) -> Self {
|
||||
options.diskStoreWriteOptions = writingOptions
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether the disk storage loading should happen in the same calling queue.
|
||||
/// - Parameter enabled: Whether the disk storage loading should happen in the same calling queue.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// By default, disk storage file loading
|
||||
/// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk
|
||||
/// loading performance, it also causes a flickering when you reload an image from disk, if the image view already
|
||||
/// has an image set.
|
||||
///
|
||||
/// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue
|
||||
/// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance.
|
||||
///
|
||||
public func loadDiskFileSynchronously(_ enabled: Bool = true) -> Self {
|
||||
options.loadDiskFileSynchronously = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets a queue on which the image processing should happen.
|
||||
/// - Parameter queue: The queue on which the image processing should happen.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// By default, Kingfisher uses a pre-defined serial
|
||||
/// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync`
|
||||
/// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of
|
||||
/// blocking the UI, especially if the processor needs a lot of time to run).
|
||||
public func processingQueue(_ queue: CallbackQueue?) -> Self {
|
||||
options.processingQueue = queue
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the alternative sources that will be used when loading of the original input `Source` fails.
|
||||
/// - Parameter sources: The alternative sources will be used.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// Values of the `sources` array will be used to start a new image loading task if the previous task
|
||||
/// fails due to an error. The image source loading process will stop as soon as a source is loaded successfully.
|
||||
/// If all `sources` are used but the loading is still failing, an `imageSettingError` with
|
||||
/// `alternativeSourcesExhausted` as its reason will be given out in the `catch` block.
|
||||
///
|
||||
/// This is useful if you want to implement a fallback solution for setting image.
|
||||
///
|
||||
/// User cancellation will not trigger the alternative source loading.
|
||||
public func alternativeSources(_ sources: [Source]?) -> Self {
|
||||
options.alternativeSources = sources
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets a retry strategy that will be used when something gets wrong during the image retrieving.
|
||||
/// - Parameter strategy: The provided strategy to define how the retrying should happen.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func retry(_ strategy: RetryStrategy?) -> Self {
|
||||
options.retryStrategy = strategy
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets a retry strategy with a max retry count and retrying interval.
|
||||
/// - Parameters:
|
||||
/// - maxCount: The maximum count before the retry stops.
|
||||
/// - interval: The time interval between each retry attempt.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// This defines the simplest retry strategy, which retry a failing request for several times, with some certain
|
||||
/// interval between each time. For example, `.retry(maxCount: 3, interval: .second(3))` means attempt for at most
|
||||
/// three times, and wait for 3 seconds if a previous retry attempt fails, then start a new attempt.
|
||||
public func retry(maxCount: Int, interval: DelayRetryStrategy.Interval = .seconds(3)) -> Self {
|
||||
let strategy = DelayRetryStrategy(maxRetryCount: maxCount, retryInterval: interval)
|
||||
options.retryStrategy = strategy
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the `Source` should be loaded when user enables Low Data Mode and the original source fails with an
|
||||
/// `NSURLErrorNetworkUnavailableReason.constrained` error.
|
||||
/// - Parameter source: The `Source` will be loaded under low data mode.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// When this option is set, the
|
||||
/// `allowsConstrainedNetworkAccess` property of the request for the original source will be set to `false` and the
|
||||
/// `Source` in associated value will be used to retrieve the image for low data mode. Usually, you can provide a
|
||||
/// low-resolution version of your image or a local image provider to display a placeholder.
|
||||
///
|
||||
/// If not set or the `source` is `nil`, the device Low Data Mode will be ignored and the original source will
|
||||
/// be loaded following the system default behavior, in a normal way.
|
||||
public func lowDataModeSource(_ source: Source?) -> Self {
|
||||
options.lowDataModeSource = source
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets whether the image setting for an image view should happen with transition even when retrieved from cache.
|
||||
/// - Parameter enabled: Enable the force transition or not.
|
||||
/// - Returns: A `Self` with changes applied.
|
||||
public func forceTransition(_ enabled: Bool = true) -> Self {
|
||||
options.forceTransition = enabled
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the image that will be used if an image retrieving task fails.
|
||||
/// - Parameter image: The image that will be used when something goes wrong.
|
||||
/// - Returns: A `Self` with changes applied.
|
||||
///
|
||||
/// If set and an image retrieving error occurred Kingfisher will set provided image (or empty)
|
||||
/// in place of requested one. It's useful when you don't want to show placeholder
|
||||
/// during loading time but wants to use some default image when requests will be failed.
|
||||
///
|
||||
public func onFailureImage(_ image: KFCrossPlatformImage?) -> Self {
|
||||
options.onFailureImage = .some(image)
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Request Modifier
|
||||
extension KFOptionSetter {
|
||||
/// Sets an `ImageDownloadRequestModifier` to change the image download request before it being sent.
|
||||
/// - Parameter modifier: The modifier will be used to change the request before it being sent.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// This is the last chance you can modify the image download 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.
|
||||
///
|
||||
public func requestModifier(_ modifier: AsyncImageDownloadRequestModifier) -> Self {
|
||||
options.requestModifier = modifier
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets a block to change the image download request before it being sent.
|
||||
/// - Parameter modifyBlock: The modifying block will be called to change the request before it being sent.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// This is the last chance you can modify the image download 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.
|
||||
///
|
||||
public func requestModifier(_ modifyBlock: @escaping (inout URLRequest) -> Void) -> Self {
|
||||
options.requestModifier = AnyModifier { r -> URLRequest? in
|
||||
var request = r
|
||||
modifyBlock(&request)
|
||||
return request
|
||||
}
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Redirect Handler
|
||||
extension KFOptionSetter {
|
||||
/// The `ImageDownloadRedirectHandler` argument will be used to change the request before redirection.
|
||||
/// This is the possibility you can modify the image download request during redirect. 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 redirection request will be sent without any modification by default.
|
||||
/// - Parameter handler: The handler will be used for redirection.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func redirectHandler(_ handler: ImageDownloadRedirectHandler) -> Self {
|
||||
options.redirectHandler = handler
|
||||
return self
|
||||
}
|
||||
|
||||
/// The `block` will be used to change the request before redirection.
|
||||
/// This is the possibility you can modify the image download request during redirect. 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 redirection request will be sent without any modification by default.
|
||||
/// - Parameter block: The block will be used for redirection.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func redirectHandler(_ block: @escaping (KF.RedirectPayload) -> Void) -> Self {
|
||||
let redirectHandler = AnyRedirectHandler { (task, response, request, handler) in
|
||||
let payload = KF.RedirectPayload(
|
||||
task: task, response: response, newRequest: request, completionHandler: handler
|
||||
)
|
||||
block(payload)
|
||||
}
|
||||
options.redirectHandler = redirectHandler
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Processor
|
||||
extension KFOptionSetter {
|
||||
|
||||
/// Sets an image processor for the image task. It replaces the current image processor settings.
|
||||
///
|
||||
/// - Parameter processor: The processor you want to use to process the image after it is downloaded.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// - Note:
|
||||
/// To append a processor to current ones instead of replacing them all, use `appendProcessor(_:)`.
|
||||
public func setProcessor(_ processor: ImageProcessor) -> Self {
|
||||
options.processor = processor
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets an array of image processors for the image task. It replaces the current image processor settings.
|
||||
/// - Parameter processors: An array of processors. The processors inside this array will be concatenated one by one
|
||||
/// to form a processor pipeline.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
///
|
||||
/// - Note: To append processors to current ones instead of replacing them all, concatenate them by `|>`, then use
|
||||
/// `appendProcessor(_:)`.
|
||||
public func setProcessors(_ processors: [ImageProcessor]) -> Self {
|
||||
switch processors.count {
|
||||
case 0:
|
||||
options.processor = DefaultImageProcessor.default
|
||||
case 1...:
|
||||
options.processor = processors.dropFirst().reduce(processors[0]) { $0 |> $1 }
|
||||
default:
|
||||
assertionFailure("Never happen")
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
/// Appends a processor to the current set processors.
|
||||
/// - Parameter processor: The processor which will be appended to current processor settings.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func appendProcessor(_ processor: ImageProcessor) -> Self {
|
||||
options.processor = options.processor |> processor
|
||||
return self
|
||||
}
|
||||
|
||||
/// Appends a `RoundCornerImageProcessor` to current processors.
|
||||
/// - Parameters:
|
||||
/// - radius: The radius will be applied in processing. Specify a certain point value with `.point`, or a fraction
|
||||
/// of the target image with `.widthFraction`. or `.heightFraction`. For example, given a square image
|
||||
/// with width and height equals, `.widthFraction(0.5)` means use half of the length of size and makes
|
||||
/// the final image a round one.
|
||||
/// - targetSize: Target size of output image should be. If `nil`, the image will keep its original size after processing.
|
||||
/// - corners: The target corners which will be applied rounding.
|
||||
/// - backgroundColor: Background color of the output image. If `nil`, it will use a transparent background.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func roundCorner(
|
||||
radius: Radius,
|
||||
targetSize: CGSize? = nil,
|
||||
roundingCorners corners: RectCorner = .all,
|
||||
backgroundColor: KFCrossPlatformColor? = nil
|
||||
) -> Self
|
||||
{
|
||||
let processor = RoundCornerImageProcessor(
|
||||
radius: radius,
|
||||
targetSize: targetSize,
|
||||
roundingCorners: corners,
|
||||
backgroundColor: backgroundColor
|
||||
)
|
||||
return appendProcessor(processor)
|
||||
}
|
||||
|
||||
/// Appends a `BlurImageProcessor` to current processors.
|
||||
/// - Parameter radius: Blur radius for the simulated Gaussian blur.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func blur(radius: CGFloat) -> Self {
|
||||
appendProcessor(
|
||||
BlurImageProcessor(blurRadius: radius)
|
||||
)
|
||||
}
|
||||
|
||||
/// Appends a `OverlayImageProcessor` to current processors.
|
||||
/// - Parameters:
|
||||
/// - color: Overlay color will be used to overlay the input image.
|
||||
/// - fraction: Fraction will be used when overlay the color to image.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func overlay(color: KFCrossPlatformColor, fraction: CGFloat = 0.5) -> Self {
|
||||
appendProcessor(
|
||||
OverlayImageProcessor(overlay: color, fraction: fraction)
|
||||
)
|
||||
}
|
||||
|
||||
/// Appends a `TintImageProcessor` to current processors.
|
||||
/// - Parameter color: Tint color will be used to tint the input image.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func tint(color: KFCrossPlatformColor) -> Self {
|
||||
appendProcessor(
|
||||
TintImageProcessor(tint: color)
|
||||
)
|
||||
}
|
||||
|
||||
/// Appends a `BlackWhiteProcessor` to current processors.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func blackWhite() -> Self {
|
||||
appendProcessor(
|
||||
BlackWhiteProcessor()
|
||||
)
|
||||
}
|
||||
|
||||
/// Appends a `CroppingImageProcessor` to current processors.
|
||||
/// - Parameters:
|
||||
/// - size: Target size of output image should be.
|
||||
/// - anchor: Anchor point from which the output size should be calculate. The anchor point is consisted by two
|
||||
/// values between 0.0 and 1.0. It indicates a related point in current image.
|
||||
/// See `CroppingImageProcessor.init(size:anchor:)` for more.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func cropping(size: CGSize, anchor: CGPoint = .init(x: 0.5, y: 0.5)) -> Self {
|
||||
appendProcessor(
|
||||
CroppingImageProcessor(size: size, anchor: anchor)
|
||||
)
|
||||
}
|
||||
|
||||
/// Appends a `DownsamplingImageProcessor` to current processors.
|
||||
///
|
||||
/// Compared to `ResizingImageProcessor`, the `DownsamplingImageProcessor` does not render the original images and
|
||||
/// then resize it. Instead, it downsamples the input data directly to a thumbnail image. So it is a more efficient
|
||||
/// than `ResizingImageProcessor`. Prefer to use `DownsamplingImageProcessor` as possible
|
||||
/// as you can than the `ResizingImageProcessor`.
|
||||
///
|
||||
/// Only CG-based images are supported. Animated images (like GIF) is not supported.
|
||||
///
|
||||
/// - Parameter size: Target size of output image should be. It should be smaller than the size of input image.
|
||||
/// If it is larger, the result image will be the same size of input data without downsampling.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func downsampling(size: CGSize) -> Self {
|
||||
let processor = DownsamplingImageProcessor(size: size)
|
||||
if options.processor == DefaultImageProcessor.default {
|
||||
return setProcessor(processor)
|
||||
} else {
|
||||
return appendProcessor(processor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Appends a `ResizingImageProcessor` to current processors.
|
||||
///
|
||||
/// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor`
|
||||
/// instead, which is more efficient and uses less memory.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - referenceSize: The reference size for resizing operation in point.
|
||||
/// - mode: Target content mode of output image should be. Default is `.none`.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func resizing(referenceSize: CGSize, mode: ContentMode = .none) -> Self {
|
||||
appendProcessor(
|
||||
ResizingImageProcessor(referenceSize: referenceSize, mode: mode)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Cache Serializer
|
||||
extension KFOptionSetter {
|
||||
|
||||
/// Uses a given `CacheSerializer` to convert some data to an image object for retrieving from disk cache or vice
|
||||
/// versa for storing to disk cache.
|
||||
/// - Parameter cacheSerializer: The `CacheSerializer` which will be used.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func serialize(by cacheSerializer: CacheSerializer) -> Self {
|
||||
options.cacheSerializer = cacheSerializer
|
||||
return self
|
||||
}
|
||||
|
||||
/// Uses a given format to serializer the image data to disk. It converts the image object to the give data format.
|
||||
/// - Parameters:
|
||||
/// - format: The desired data encoding format when store the image on disk.
|
||||
/// - jpegCompressionQuality: If the format is `.JPEG`, it specify the compression quality when converting the
|
||||
/// image to a JPEG data. Otherwise, it is ignored.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func serialize(as format: ImageFormat, jpegCompressionQuality: CGFloat? = nil) -> Self {
|
||||
let cacheSerializer: FormatIndicatedCacheSerializer
|
||||
switch format {
|
||||
case .JPEG:
|
||||
cacheSerializer = .jpeg(compressionQuality: jpegCompressionQuality ?? 1.0)
|
||||
case .PNG:
|
||||
cacheSerializer = .png
|
||||
case .GIF:
|
||||
cacheSerializer = .gif
|
||||
case .unknown:
|
||||
cacheSerializer = .png
|
||||
}
|
||||
options.cacheSerializer = cacheSerializer
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Image Modifier
|
||||
extension KFOptionSetter {
|
||||
|
||||
/// Sets an `ImageModifier` to the image task. Use this to modify the fetched image object properties if needed.
|
||||
///
|
||||
/// If the image was fetched directly from the downloader, the modifier will run directly after the
|
||||
/// `ImageProcessor`. If the image is being fetched from a cache, the modifier will run after the `CacheSerializer`.
|
||||
/// - Parameter modifier: The `ImageModifier` which will be used to modify the image object.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func imageModifier(_ modifier: ImageModifier?) -> Self {
|
||||
options.imageModifier = modifier
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets a block to modify the image object. Use this to modify the fetched image object properties if needed.
|
||||
///
|
||||
/// If the image was fetched directly from the downloader, the modifier block will run directly after the
|
||||
/// `ImageProcessor`. If the image is being fetched from a cache, the modifier will run after the `CacheSerializer`.
|
||||
///
|
||||
/// - Parameter block: The block which is used to modify the image object.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func imageModifier(_ block: @escaping (inout KFCrossPlatformImage) throws -> Void) -> Self {
|
||||
let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
|
||||
var image = image
|
||||
try block(&image)
|
||||
return image
|
||||
}
|
||||
options.imageModifier = modifier
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Cache Expiration
|
||||
extension KFOptionSetter {
|
||||
|
||||
/// Sets the expiration setting for memory cache of this image task.
|
||||
///
|
||||
/// By default, the underlying `MemoryStorage.Backend` uses the
|
||||
/// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this value to overwrite
|
||||
/// the config setting for this caching item.
|
||||
///
|
||||
/// - Parameter expiration: The expiration setting used in cache storage.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func memoryCacheExpiration(_ expiration: StorageExpiration?) -> Self {
|
||||
options.memoryCacheExpiration = expiration
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the expiration extending setting for memory cache. The item expiration time will be incremented by this
|
||||
/// value after access.
|
||||
///
|
||||
/// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending
|
||||
/// value: .cacheTime.
|
||||
///
|
||||
/// To disable extending option at all, sets `.none` to it.
|
||||
///
|
||||
/// - Parameter extending: The expiration extending setting used in cache storage.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func memoryCacheAccessExtending(_ extending: ExpirationExtending) -> Self {
|
||||
options.memoryCacheAccessExtendingExpiration = extending
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the expiration setting for disk cache of this image task.
|
||||
///
|
||||
/// By default, the underlying `DiskStorage.Backend` uses the expiration in its config for all items. If set,
|
||||
/// the `DiskStorage.Backend` will use this value to overwrite the config setting for this caching item.
|
||||
///
|
||||
/// - Parameter expiration: The expiration setting used in cache storage.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func diskCacheExpiration(_ expiration: StorageExpiration?) -> Self {
|
||||
options.diskCacheExpiration = expiration
|
||||
return self
|
||||
}
|
||||
|
||||
/// Sets the expiration extending setting for disk cache. The item expiration time will be incremented by this
|
||||
/// value after access.
|
||||
///
|
||||
/// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending
|
||||
/// value: .cacheTime.
|
||||
///
|
||||
/// To disable extending option at all, sets `.none` to it.
|
||||
///
|
||||
/// - Parameter extending: The expiration extending setting used in cache storage.
|
||||
/// - Returns: A `Self` value with changes applied.
|
||||
public func diskCacheAccessExtending(_ extending: ExpirationExtending) -> Self {
|
||||
options.diskCacheAccessExtendingExpiration = extending
|
||||
return self
|
||||
}
|
||||
}
|
||||
106
Pods/Kingfisher/Sources/General/Kingfisher.swift
generated
Normal file
106
Pods/Kingfisher/Sources/General/Kingfisher.swift
generated
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// Kingfisher.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 16/9/14.
|
||||
//
|
||||
// Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
import ImageIO
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
public typealias KFCrossPlatformImage = NSImage
|
||||
public typealias KFCrossPlatformView = NSView
|
||||
public typealias KFCrossPlatformColor = NSColor
|
||||
public typealias KFCrossPlatformImageView = NSImageView
|
||||
public typealias KFCrossPlatformButton = NSButton
|
||||
#else
|
||||
import UIKit
|
||||
public typealias KFCrossPlatformImage = UIImage
|
||||
public typealias KFCrossPlatformColor = UIColor
|
||||
#if !os(watchOS)
|
||||
public typealias KFCrossPlatformImageView = UIImageView
|
||||
public typealias KFCrossPlatformView = UIView
|
||||
public typealias KFCrossPlatformButton = UIButton
|
||||
#if canImport(TVUIKit)
|
||||
import TVUIKit
|
||||
#endif
|
||||
#if canImport(CarPlay) && !targetEnvironment(macCatalyst)
|
||||
import CarPlay
|
||||
#endif
|
||||
#else
|
||||
import WatchKit
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// Wrapper for Kingfisher compatible types. This type provides an extension point for
|
||||
/// convenience methods in Kingfisher.
|
||||
public struct KingfisherWrapper<Base> {
|
||||
public let base: Base
|
||||
public init(_ base: Base) {
|
||||
self.base = base
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an object type that is compatible with Kingfisher. You can use `kf` property to get a
|
||||
/// value in the namespace of Kingfisher.
|
||||
public protocol KingfisherCompatible: AnyObject { }
|
||||
|
||||
/// Represents a value type that is compatible with Kingfisher. You can use `kf` property to get a
|
||||
/// value in the namespace of Kingfisher.
|
||||
public protocol KingfisherCompatibleValue {}
|
||||
|
||||
extension KingfisherCompatible {
|
||||
/// Gets a namespace holder for Kingfisher compatible types.
|
||||
public var kf: KingfisherWrapper<Self> {
|
||||
get { return KingfisherWrapper(self) }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherCompatibleValue {
|
||||
/// Gets a namespace holder for Kingfisher compatible types.
|
||||
public var kf: KingfisherWrapper<Self> {
|
||||
get { return KingfisherWrapper(self) }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
extension KFCrossPlatformImage: KingfisherCompatible { }
|
||||
#if !os(watchOS)
|
||||
extension KFCrossPlatformImageView: KingfisherCompatible { }
|
||||
extension KFCrossPlatformButton: KingfisherCompatible { }
|
||||
extension NSTextAttachment: KingfisherCompatible { }
|
||||
#else
|
||||
extension WKInterfaceImage: KingfisherCompatible { }
|
||||
#endif
|
||||
|
||||
#if os(tvOS) && canImport(TVUIKit)
|
||||
@available(tvOS 12.0, *)
|
||||
extension TVMonogramView: KingfisherCompatible { }
|
||||
#endif
|
||||
|
||||
#if canImport(CarPlay) && !targetEnvironment(macCatalyst)
|
||||
@available(iOS 14.0, *)
|
||||
extension CPListItem: KingfisherCompatible { }
|
||||
#endif
|
||||
469
Pods/Kingfisher/Sources/General/KingfisherError.swift
generated
Normal file
469
Pods/Kingfisher/Sources/General/KingfisherError.swift
generated
Normal file
@@ -0,0 +1,469 @@
|
||||
//
|
||||
// KingfisherError.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2018/09/26.
|
||||
//
|
||||
// Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Never {}
|
||||
|
||||
/// Represents all the errors which can happen in Kingfisher framework.
|
||||
/// Kingfisher related methods always throw a `KingfisherError` or invoke the callback with `KingfisherError`
|
||||
/// as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog,
|
||||
/// then switch over the reason to know error detail.
|
||||
public enum KingfisherError: Error {
|
||||
|
||||
// MARK: Error Reason Types
|
||||
|
||||
/// Represents the error reason during networking request phase.
|
||||
///
|
||||
/// - emptyRequest: The request is empty. Code 1001.
|
||||
/// - invalidURL: The URL of request is invalid. Code 1002.
|
||||
/// - taskCancelled: The downloading task is cancelled by user. Code 1003.
|
||||
public enum RequestErrorReason {
|
||||
|
||||
/// The request is empty. Code 1001.
|
||||
case emptyRequest
|
||||
|
||||
/// The URL of request is invalid. Code 1002.
|
||||
/// - request: The request is tend to be sent but its URL is invalid.
|
||||
case invalidURL(request: URLRequest)
|
||||
|
||||
/// The downloading task is cancelled by user. Code 1003.
|
||||
/// - task: The session data task which is cancelled.
|
||||
/// - token: The cancel token which is used for cancelling the task.
|
||||
case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken)
|
||||
}
|
||||
|
||||
/// Represents the error reason during networking response phase.
|
||||
///
|
||||
/// - invalidURLResponse: The response is not a valid URL response. Code 2001.
|
||||
/// - invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002.
|
||||
/// - URLSessionError: An error happens in the system URL session. Code 2003.
|
||||
/// - dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004.
|
||||
/// - noURLResponse: The task is done but no URL response found. Code 2005.
|
||||
public enum ResponseErrorReason {
|
||||
|
||||
/// The response is not a valid URL response. Code 2001.
|
||||
/// - response: The received invalid URL response.
|
||||
/// The response is expected to be an HTTP response, but it is not.
|
||||
case invalidURLResponse(response: URLResponse)
|
||||
|
||||
/// The response contains an invalid HTTP status code. Code 2002.
|
||||
/// - Note:
|
||||
/// By default, status code 200..<400 is recognized as valid. You can override
|
||||
/// this behavior by conforming to the `ImageDownloaderDelegate`.
|
||||
/// - response: The received response.
|
||||
case invalidHTTPStatusCode(response: HTTPURLResponse)
|
||||
|
||||
/// An error happens in the system URL session. Code 2003.
|
||||
/// - error: The underlying URLSession error object.
|
||||
case URLSessionError(error: Error)
|
||||
|
||||
/// Data modifying fails on returning a valid data. Code 2004.
|
||||
/// - task: The failed task.
|
||||
case dataModifyingFailed(task: SessionDataTask)
|
||||
|
||||
/// The task is done but no URL response found. Code 2005.
|
||||
/// - task: The failed task.
|
||||
case noURLResponse(task: SessionDataTask)
|
||||
|
||||
/// The task is cancelled by `ImageDownloaderDelegate` due to the `.cancel` response disposition is
|
||||
/// specified by the delegate method. Code 2006.
|
||||
case cancelledByDelegate(response: URLResponse)
|
||||
}
|
||||
|
||||
/// Represents the error reason during Kingfisher caching system.
|
||||
///
|
||||
/// - fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001.
|
||||
/// - invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002.
|
||||
/// - invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003.
|
||||
/// - cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004.
|
||||
/// - cannotCreateDirectory: Cannot create a folder at a given path. Code 3005.
|
||||
/// - imageNotExisting: The requested image does not exist in cache. Code 3006.
|
||||
/// - cannotConvertToData: Cannot convert an object to data for storing. Code 3007.
|
||||
/// - cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008.
|
||||
/// - cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009.
|
||||
/// - cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010.
|
||||
public enum CacheErrorReason {
|
||||
|
||||
/// Cannot create a file enumerator for a certain disk URL. Code 3001.
|
||||
/// - url: The target disk URL from which the file enumerator should be created.
|
||||
case fileEnumeratorCreationFailed(url: URL)
|
||||
|
||||
/// Cannot get correct file contents from a file enumerator. Code 3002.
|
||||
/// - url: The target disk URL from which the content of a file enumerator should be got.
|
||||
case invalidFileEnumeratorContent(url: URL)
|
||||
|
||||
/// The file at target URL exists, but its URL resource is unavailable. Code 3003.
|
||||
/// - error: The underlying error thrown by file manager.
|
||||
/// - key: The key used to getting the resource from cache.
|
||||
/// - url: The disk URL where the target cached file exists.
|
||||
case invalidURLResource(error: Error, key: String, url: URL)
|
||||
|
||||
/// The file at target URL exists, but the data cannot be loaded from it. Code 3004.
|
||||
/// - url: The disk URL where the target cached file exists.
|
||||
/// - error: The underlying error which describes why this error happens.
|
||||
case cannotLoadDataFromDisk(url: URL, error: Error)
|
||||
|
||||
/// Cannot create a folder at a given path. Code 3005.
|
||||
/// - path: The disk path where the directory creating operation fails.
|
||||
/// - error: The underlying error which describes why this error happens.
|
||||
case cannotCreateDirectory(path: String, error: Error)
|
||||
|
||||
/// The requested image does not exist in cache. Code 3006.
|
||||
/// - key: Key of the requested image in cache.
|
||||
case imageNotExisting(key: String)
|
||||
|
||||
/// Cannot convert an object to data for storing. Code 3007.
|
||||
/// - object: The object which needs be convert to data.
|
||||
case cannotConvertToData(object: Any, error: Error)
|
||||
|
||||
/// Cannot serialize an image to data for storing. Code 3008.
|
||||
/// - image: The input image needs to be serialized to cache.
|
||||
/// - original: The original image data, if exists.
|
||||
/// - serializer: The `CacheSerializer` used for the image serializing.
|
||||
case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: CacheSerializer)
|
||||
|
||||
/// Cannot create the cache file at a certain fileURL under a key. Code 3009.
|
||||
/// - fileURL: The url where the cache file should be created.
|
||||
/// - key: The cache key used for the cache. When caching a file through `KingfisherManager` and Kingfisher's
|
||||
/// extension method, it is the resolved cache key based on your input `Source` and the image processors.
|
||||
/// - data: The data to be cached.
|
||||
/// - error: The underlying error originally thrown by Foundation when writing the `data` to the disk file at
|
||||
/// `fileURL`.
|
||||
case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: Error)
|
||||
|
||||
/// Cannot set file attributes to a cached file. Code 3010.
|
||||
/// - filePath: The path of target cache file.
|
||||
/// - attributes: The file attribute to be set to the target file.
|
||||
/// - error: The underlying error originally thrown by Foundation when setting the `attributes` to the disk
|
||||
/// file at `filePath`.
|
||||
case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: Error)
|
||||
|
||||
/// The disk storage of cache is not ready. Code 3011.
|
||||
///
|
||||
/// This is usually due to extremely lack of space on disk storage, and
|
||||
/// Kingfisher failed even when creating the cache folder. The disk storage will be in unusable state. Normally,
|
||||
/// ask user to free some spaces and restart the app to make the disk storage work again.
|
||||
/// - cacheURL: The intended URL which should be the storage folder.
|
||||
case diskStorageIsNotReady(cacheURL: URL)
|
||||
}
|
||||
|
||||
|
||||
/// Represents the error reason during image processing phase.
|
||||
///
|
||||
/// - processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001.
|
||||
public enum ProcessorErrorReason {
|
||||
/// Image processing fails. There is no valid output image from the processor. Code 4001.
|
||||
/// - processor: The `ImageProcessor` used to process the image or its data in `item`.
|
||||
/// - item: The image or its data content.
|
||||
case processingFailed(processor: ImageProcessor, item: ImageProcessItem)
|
||||
}
|
||||
|
||||
/// Represents the error reason during image setting in a view related class.
|
||||
///
|
||||
/// - emptySource: The input resource is empty or `nil`. Code 5001.
|
||||
/// - notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002.
|
||||
/// - dataProviderError: An error happens during getting data from an `ImageDataProvider`. Code 5003.
|
||||
public enum ImageSettingErrorReason {
|
||||
|
||||
/// The input resource is empty or `nil`. Code 5001.
|
||||
case emptySource
|
||||
|
||||
/// The resource task is finished, but it is not the one expected now. This usually happens when you set another
|
||||
/// resource on the view without cancelling the current on-going one. The previous setting task will fail with
|
||||
/// this `.notCurrentSourceTask` error when a result got, regardless of it being successful or not for that task.
|
||||
/// The result of this original task is contained in the associated value.
|
||||
/// Code 5002.
|
||||
/// - result: The `RetrieveImageResult` if the source task is finished without problem. `nil` if an error
|
||||
/// happens.
|
||||
/// - error: The `Error` if an issue happens during image setting task. `nil` if the task finishes without
|
||||
/// problem.
|
||||
/// - source: The original source value of the task.
|
||||
case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source)
|
||||
|
||||
/// An error happens during getting data from an `ImageDataProvider`. Code 5003.
|
||||
case dataProviderError(provider: ImageDataProvider, error: Error)
|
||||
|
||||
/// No more alternative `Source` can be used in current loading process. It means that the
|
||||
/// `.alternativeSources` are used and Kingfisher tried to recovery from the original error, but still
|
||||
/// fails for all the given alternative sources. The associated value holds all the errors encountered during
|
||||
/// the load process, including the original source loading error and all the alternative sources errors.
|
||||
/// Code 5004.
|
||||
case alternativeSourcesExhausted([PropagationError])
|
||||
}
|
||||
|
||||
// MARK: Member Cases
|
||||
|
||||
/// Represents the error reason during networking request phase.
|
||||
case requestError(reason: RequestErrorReason)
|
||||
/// Represents the error reason during networking response phase.
|
||||
case responseError(reason: ResponseErrorReason)
|
||||
/// Represents the error reason during Kingfisher caching system.
|
||||
case cacheError(reason: CacheErrorReason)
|
||||
/// Represents the error reason during image processing phase.
|
||||
case processorError(reason: ProcessorErrorReason)
|
||||
/// Represents the error reason during image setting in a view related class.
|
||||
case imageSettingError(reason: ImageSettingErrorReason)
|
||||
|
||||
// MARK: Helper Properties & Methods
|
||||
|
||||
/// Helper property to check whether this error is a `RequestErrorReason.taskCancelled` or not.
|
||||
public var isTaskCancelled: Bool {
|
||||
if case .requestError(reason: .taskCancelled) = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Helper method to check whether this error is a `ResponseErrorReason.invalidHTTPStatusCode` and the
|
||||
/// associated value is a given status code.
|
||||
///
|
||||
/// - Parameter code: The given status code.
|
||||
/// - Returns: If `self` is a `ResponseErrorReason.invalidHTTPStatusCode` error
|
||||
/// and its status code equals to `code`, `true` is returned. Otherwise, `false`.
|
||||
public func isInvalidResponseStatusCode(_ code: Int) -> Bool {
|
||||
if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self {
|
||||
return response.statusCode == code
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public var isInvalidResponseStatusCode: Bool {
|
||||
if case .responseError(reason: .invalidHTTPStatusCode) = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// Helper property to check whether this error is a `ImageSettingErrorReason.notCurrentSourceTask` or not.
|
||||
/// When a new image setting task starts while the old one is still running, the new task identifier will be
|
||||
/// set and the old one is overwritten. A `.notCurrentSourceTask` error will be raised when the old task finishes
|
||||
/// to let you know the setting process finishes with a certain result, but the image view or button is not set.
|
||||
public var isNotCurrentTask: Bool {
|
||||
if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isLowDataModeConstrained: Bool {
|
||||
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *),
|
||||
case .responseError(reason: .URLSessionError(let sessionError)) = self,
|
||||
let urlError = sessionError as? URLError,
|
||||
urlError.networkUnavailableReason == .constrained
|
||||
{
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - LocalizedError Conforming
|
||||
extension KingfisherError: LocalizedError {
|
||||
|
||||
/// A localized message describing what error occurred.
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .requestError(let reason): return reason.errorDescription
|
||||
case .responseError(let reason): return reason.errorDescription
|
||||
case .cacheError(let reason): return reason.errorDescription
|
||||
case .processorError(let reason): return reason.errorDescription
|
||||
case .imageSettingError(let reason): return reason.errorDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CustomNSError Conforming
|
||||
extension KingfisherError: CustomNSError {
|
||||
|
||||
/// The error domain of `KingfisherError`. All errors from Kingfisher is under this domain.
|
||||
public static let domain = "com.onevcat.Kingfisher.Error"
|
||||
|
||||
/// The error code within the given domain.
|
||||
public var errorCode: Int {
|
||||
switch self {
|
||||
case .requestError(let reason): return reason.errorCode
|
||||
case .responseError(let reason): return reason.errorCode
|
||||
case .cacheError(let reason): return reason.errorCode
|
||||
case .processorError(let reason): return reason.errorCode
|
||||
case .imageSettingError(let reason): return reason.errorCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherError.RequestErrorReason {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .emptyRequest:
|
||||
return "The request is empty or `nil`."
|
||||
case .invalidURL(let request):
|
||||
return "The request contains an invalid or empty URL. Request: \(request)."
|
||||
case .taskCancelled(let task, let token):
|
||||
return "The session task was cancelled. Task: \(task), cancel token: \(token)."
|
||||
}
|
||||
}
|
||||
|
||||
var errorCode: Int {
|
||||
switch self {
|
||||
case .emptyRequest: return 1001
|
||||
case .invalidURL: return 1002
|
||||
case .taskCancelled: return 1003
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherError.ResponseErrorReason {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURLResponse(let response):
|
||||
return "The URL response is invalid: \(response)"
|
||||
case .invalidHTTPStatusCode(let response):
|
||||
return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)."
|
||||
case .URLSessionError(let error):
|
||||
return "A URL session error happened. The underlying error: \(error)"
|
||||
case .dataModifyingFailed(let task):
|
||||
return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)."
|
||||
case .noURLResponse(let task):
|
||||
return "No URL response received. Task: \(task)."
|
||||
case .cancelledByDelegate(let response):
|
||||
return "The downloading task is cancelled by the downloader delegate. Response: \(response)."
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var errorCode: Int {
|
||||
switch self {
|
||||
case .invalidURLResponse: return 2001
|
||||
case .invalidHTTPStatusCode: return 2002
|
||||
case .URLSessionError: return 2003
|
||||
case .dataModifyingFailed: return 2004
|
||||
case .noURLResponse: return 2005
|
||||
case .cancelledByDelegate: return 2006
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherError.CacheErrorReason {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .fileEnumeratorCreationFailed(let url):
|
||||
return "Cannot create file enumerator for URL: \(url)."
|
||||
case .invalidFileEnumeratorContent(let url):
|
||||
return "Cannot get contents from the file enumerator at URL: \(url)."
|
||||
case .invalidURLResource(let error, let key, let url):
|
||||
return "Cannot get URL resource values or data for the given URL: \(url). " +
|
||||
"Cache key: \(key). Underlying error: \(error)"
|
||||
case .cannotLoadDataFromDisk(let url, let error):
|
||||
return "Cannot load data from disk at URL: \(url). Underlying error: \(error)"
|
||||
case .cannotCreateDirectory(let path, let error):
|
||||
return "Cannot create directory at given path: Path: \(path). Underlying error: \(error)"
|
||||
case .imageNotExisting(let key):
|
||||
return "The image is not in cache, but you requires it should only be " +
|
||||
"from cache by enabling the `.onlyFromCache` option. Key: \(key)."
|
||||
case .cannotConvertToData(let object, let error):
|
||||
return "Cannot convert the input object to a `Data` object when storing it to disk cache. " +
|
||||
"Object: \(object). Underlying error: \(error)"
|
||||
case .cannotSerializeImage(let image, let originalData, let serializer):
|
||||
return "Cannot serialize an image due to the cache serializer returning `nil`. " +
|
||||
"Image: \(String(describing:image)), original data: \(String(describing: originalData)), " +
|
||||
"serializer: \(serializer)."
|
||||
case .cannotCreateCacheFile(let fileURL, let key, let data, let error):
|
||||
return "Cannot create cache file at url: \(fileURL), key: \(key), data length: \(data.count). " +
|
||||
"Underlying foundation error: \(error)."
|
||||
case .cannotSetCacheFileAttribute(let filePath, let attributes, let error):
|
||||
return "Cannot set file attribute for the cache file at path: \(filePath), attributes: \(attributes)." +
|
||||
"Underlying foundation error: \(error)."
|
||||
case .diskStorageIsNotReady(let cacheURL):
|
||||
return "The disk storage is not ready to use yet at URL: '\(cacheURL)'. " +
|
||||
"This is usually caused by extremely lack of disk space. Ask users to free up some space and restart the app."
|
||||
}
|
||||
}
|
||||
|
||||
var errorCode: Int {
|
||||
switch self {
|
||||
case .fileEnumeratorCreationFailed: return 3001
|
||||
case .invalidFileEnumeratorContent: return 3002
|
||||
case .invalidURLResource: return 3003
|
||||
case .cannotLoadDataFromDisk: return 3004
|
||||
case .cannotCreateDirectory: return 3005
|
||||
case .imageNotExisting: return 3006
|
||||
case .cannotConvertToData: return 3007
|
||||
case .cannotSerializeImage: return 3008
|
||||
case .cannotCreateCacheFile: return 3009
|
||||
case .cannotSetCacheFileAttribute: return 3010
|
||||
case .diskStorageIsNotReady: return 3011
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherError.ProcessorErrorReason {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .processingFailed(let processor, let item):
|
||||
return "Processing image failed. Processor: \(processor). Processing item: \(item)."
|
||||
}
|
||||
}
|
||||
|
||||
var errorCode: Int {
|
||||
switch self {
|
||||
case .processingFailed: return 4001
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherError.ImageSettingErrorReason {
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .emptySource:
|
||||
return "The input resource is empty."
|
||||
case .notCurrentSourceTask(let result, let error, let resource):
|
||||
if let result = result {
|
||||
return "Retrieving resource succeeded, but this source is " +
|
||||
"not the one currently expected. Result: \(result). Resource: \(resource)."
|
||||
} else if let error = error {
|
||||
return "Retrieving resource failed, and this resource is " +
|
||||
"not the one currently expected. Error: \(error). Resource: \(resource)."
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .dataProviderError(let provider, let error):
|
||||
return "Image data provider fails to provide data. Provider: \(provider), error: \(error)"
|
||||
case .alternativeSourcesExhausted(let errors):
|
||||
return "Image setting from alternaive sources failed: \(errors)"
|
||||
}
|
||||
}
|
||||
|
||||
var errorCode: Int {
|
||||
switch self {
|
||||
case .emptySource: return 5001
|
||||
case .notCurrentSourceTask: return 5002
|
||||
case .dataProviderError: return 5003
|
||||
case .alternativeSourcesExhausted: return 5004
|
||||
}
|
||||
}
|
||||
}
|
||||
802
Pods/Kingfisher/Sources/General/KingfisherManager.swift
generated
Normal file
802
Pods/Kingfisher/Sources/General/KingfisherManager.swift
generated
Normal file
@@ -0,0 +1,802 @@
|
||||
//
|
||||
// KingfisherManager.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 15/4/6.
|
||||
//
|
||||
// Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The downloading progress block type.
|
||||
/// The parameter value is the `receivedSize` of current response.
|
||||
/// The second parameter is the total expected data length from response's "Content-Length" header.
|
||||
/// If the expected length is not available, this block will not be called.
|
||||
public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> Void)
|
||||
|
||||
/// Represents the result of a Kingfisher retrieving image task.
|
||||
public struct RetrieveImageResult {
|
||||
/// Gets the image object of this result.
|
||||
public let image: KFCrossPlatformImage
|
||||
|
||||
/// Gets the cache source of the image. It indicates from which layer of cache this image is retrieved.
|
||||
/// If the image is just downloaded from network, `.none` will be returned.
|
||||
public let cacheType: CacheType
|
||||
|
||||
/// The `Source` which this result is related to. This indicated where the `image` of `self` is referring.
|
||||
public let source: Source
|
||||
|
||||
/// The original `Source` from which the retrieve task begins. It can be different from the `source` property.
|
||||
/// When an alternative source loading happened, the `source` will be the replacing loading target, while the
|
||||
/// `originalSource` will be kept as the initial `source` which issued the image loading process.
|
||||
public let originalSource: Source
|
||||
|
||||
/// Gets the data behind the result.
|
||||
///
|
||||
/// If this result is from a network downloading (when `cacheType == .none`), calling this returns the downloaded
|
||||
/// data. If the reuslt is from cache, it serializes the image with the given cache serializer in the loading option
|
||||
/// and returns the result.
|
||||
///
|
||||
/// - Note:
|
||||
/// This can be a time-consuming action, so if you need to use the data for multiple times, it is suggested to hold
|
||||
/// it and prevent keeping calling this too frequently.
|
||||
public let data: () -> Data?
|
||||
}
|
||||
|
||||
/// A struct that stores some related information of an `KingfisherError`. It provides some context information for
|
||||
/// a pure error so you can identify the error easier.
|
||||
public struct PropagationError {
|
||||
|
||||
/// The `Source` to which current `error` is bound.
|
||||
public let source: Source
|
||||
|
||||
/// The actual error happens in framework.
|
||||
public let error: KingfisherError
|
||||
}
|
||||
|
||||
|
||||
/// The downloading task updated block type. The parameter `newTask` is the updated new task of image setting process.
|
||||
/// It is a `nil` if the image loading does not require an image downloading process. If an image downloading is issued,
|
||||
/// this value will contain the actual `DownloadTask` for you to keep and cancel it later if you need.
|
||||
public typealias DownloadTaskUpdatedBlock = ((_ newTask: DownloadTask?) -> Void)
|
||||
|
||||
/// Main manager class of Kingfisher. It connects Kingfisher downloader and cache,
|
||||
/// to provide a set of convenience methods to use Kingfisher for tasks.
|
||||
/// You can use this class to retrieve an image via a specified URL from web or cache.
|
||||
public class KingfisherManager {
|
||||
|
||||
/// Represents a shared manager used across Kingfisher.
|
||||
/// Use this instance for getting or storing images with Kingfisher.
|
||||
public static let shared = KingfisherManager()
|
||||
|
||||
// Mark: Public Properties
|
||||
/// The `ImageCache` used by this manager. It is `ImageCache.default` by default.
|
||||
/// If a cache is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be
|
||||
/// used instead.
|
||||
public var cache: ImageCache
|
||||
|
||||
/// The `ImageDownloader` used by this manager. It is `ImageDownloader.default` by default.
|
||||
/// If a downloader is specified in `KingfisherManager.defaultOptions`, the value in `defaultOptions` will be
|
||||
/// used instead.
|
||||
public var downloader: ImageDownloader
|
||||
|
||||
/// Default options used by the manager. This option will be used in
|
||||
/// Kingfisher manager related methods, as well as all view extension methods.
|
||||
/// You can also passing other options for each image task by sending an `options` parameter
|
||||
/// to Kingfisher's APIs. The per image options will overwrite the default ones,
|
||||
/// if the option exists in both.
|
||||
public var defaultOptions = KingfisherOptionsInfo.empty
|
||||
|
||||
// Use `defaultOptions` to overwrite the `downloader` and `cache`.
|
||||
private var currentDefaultOptions: KingfisherOptionsInfo {
|
||||
return [.downloader(downloader), .targetCache(cache)] + defaultOptions
|
||||
}
|
||||
|
||||
private let processingQueue: CallbackQueue
|
||||
|
||||
private convenience init() {
|
||||
self.init(downloader: .default, cache: .default)
|
||||
}
|
||||
|
||||
/// Creates an image setting manager with specified downloader and cache.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The image downloader used to download images.
|
||||
/// - cache: The image cache which stores memory and disk images.
|
||||
public init(downloader: ImageDownloader, cache: ImageCache) {
|
||||
self.downloader = downloader
|
||||
self.cache = cache
|
||||
|
||||
let processQueueName = "com.onevcat.Kingfisher.KingfisherManager.processQueue.\(UUID().uuidString)"
|
||||
processingQueue = .dispatch(DispatchQueue(label: processQueueName))
|
||||
}
|
||||
|
||||
// MARK: - Getting Images
|
||||
|
||||
/// Gets an image from a given resource.
|
||||
/// - Parameters:
|
||||
/// - resource: The `Resource` object defines data information like key or URL.
|
||||
/// - options: Options to use when creating the image.
|
||||
/// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an
|
||||
/// `expectedContentLength`, this block will not be called. `progressBlock` is always called in
|
||||
/// main queue.
|
||||
/// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This
|
||||
/// usually happens when an alternative source is used to replace the original (failed)
|
||||
/// task. You can update your reference of `DownloadTask` if you want to manually `cancel`
|
||||
/// the new task.
|
||||
/// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked
|
||||
/// from the `options.callbackQueue`. If not specified, the main queue will be used.
|
||||
/// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource,
|
||||
/// the started `DownloadTask` is returned. Otherwise, `nil` is returned.
|
||||
///
|
||||
/// - Note:
|
||||
/// This method will first check whether the requested `resource` is already in cache or not. If cached,
|
||||
/// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it
|
||||
/// will download the `resource`, store it in cache, then call `completionHandler`.
|
||||
@discardableResult
|
||||
public func retrieveImage(
|
||||
with resource: Resource,
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
progressBlock: DownloadProgressBlock? = nil,
|
||||
downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
|
||||
{
|
||||
return retrieveImage(
|
||||
with: resource.convertToSource(),
|
||||
options: options,
|
||||
progressBlock: progressBlock,
|
||||
downloadTaskUpdated: downloadTaskUpdated,
|
||||
completionHandler: completionHandler
|
||||
)
|
||||
}
|
||||
|
||||
/// Gets an image from a given resource.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - source: The `Source` object defines data information from network or a data provider.
|
||||
/// - options: Options to use when creating the image.
|
||||
/// - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an
|
||||
/// `expectedContentLength`, this block will not be called. `progressBlock` is always called in
|
||||
/// main queue.
|
||||
/// - downloadTaskUpdated: Called when a new image downloading task is created for current image retrieving. This
|
||||
/// usually happens when an alternative source is used to replace the original (failed)
|
||||
/// task. You can update your reference of `DownloadTask` if you want to manually `cancel`
|
||||
/// the new task.
|
||||
/// - completionHandler: Called when the image retrieved and set finished. This completion handler will be invoked
|
||||
/// from the `options.callbackQueue`. If not specified, the main queue will be used.
|
||||
/// - Returns: A task represents the image downloading. If there is a download task starts for `.network` resource,
|
||||
/// the started `DownloadTask` is returned. Otherwise, `nil` is returned.
|
||||
///
|
||||
/// - Note:
|
||||
/// This method will first check whether the requested `source` is already in cache or not. If cached,
|
||||
/// it returns `nil` and invoke the `completionHandler` after the cached image retrieved. Otherwise, it
|
||||
/// will try to load the `source`, store it in cache, then call `completionHandler`.
|
||||
///
|
||||
@discardableResult
|
||||
public func retrieveImage(
|
||||
with source: Source,
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
progressBlock: DownloadProgressBlock? = nil,
|
||||
downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
|
||||
{
|
||||
let options = currentDefaultOptions + (options ?? .empty)
|
||||
let info = KingfisherParsedOptionsInfo(options)
|
||||
return retrieveImage(
|
||||
with: source,
|
||||
options: info,
|
||||
progressBlock: progressBlock,
|
||||
downloadTaskUpdated: downloadTaskUpdated,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func retrieveImage(
|
||||
with source: Source,
|
||||
options: KingfisherParsedOptionsInfo,
|
||||
progressBlock: DownloadProgressBlock? = nil,
|
||||
downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
|
||||
{
|
||||
var info = options
|
||||
if let block = progressBlock {
|
||||
info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
|
||||
}
|
||||
return retrieveImage(
|
||||
with: source,
|
||||
options: info,
|
||||
downloadTaskUpdated: downloadTaskUpdated,
|
||||
progressiveImageSetter: nil,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func retrieveImage(
|
||||
with source: Source,
|
||||
options: KingfisherParsedOptionsInfo,
|
||||
downloadTaskUpdated: DownloadTaskUpdatedBlock? = nil,
|
||||
progressiveImageSetter: ((KFCrossPlatformImage?) -> Void)? = nil,
|
||||
referenceTaskIdentifierChecker: (() -> Bool)? = nil,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
|
||||
{
|
||||
var options = options
|
||||
if let provider = ImageProgressiveProvider(options, refresh: { image in
|
||||
guard let setter = progressiveImageSetter else {
|
||||
return
|
||||
}
|
||||
guard let strategy = options.progressiveJPEG?.onImageUpdated(image) else {
|
||||
setter(image)
|
||||
return
|
||||
}
|
||||
switch strategy {
|
||||
case .default: setter(image)
|
||||
case .keepCurrent: break
|
||||
case .replace(let newImage): setter(newImage)
|
||||
}
|
||||
}) {
|
||||
options.onDataReceived = (options.onDataReceived ?? []) + [provider]
|
||||
}
|
||||
if let checker = referenceTaskIdentifierChecker {
|
||||
options.onDataReceived?.forEach {
|
||||
$0.onShouldApply = checker
|
||||
}
|
||||
}
|
||||
|
||||
let retrievingContext = RetrievingContext(options: options, originalSource: source)
|
||||
var retryContext: RetryContext?
|
||||
|
||||
func startNewRetrieveTask(
|
||||
with source: Source,
|
||||
downloadTaskUpdated: DownloadTaskUpdatedBlock?
|
||||
) {
|
||||
let newTask = self.retrieveImage(with: source, context: retrievingContext) { result in
|
||||
handler(currentSource: source, result: result)
|
||||
}
|
||||
downloadTaskUpdated?(newTask)
|
||||
}
|
||||
|
||||
func failCurrentSource(_ source: Source, with error: KingfisherError) {
|
||||
// Skip alternative sources if the user cancelled it.
|
||||
guard !error.isTaskCancelled else {
|
||||
completionHandler?(.failure(error))
|
||||
return
|
||||
}
|
||||
// When low data mode constrained error, retry with the low data mode source instead of use alternative on fly.
|
||||
guard !error.isLowDataModeConstrained else {
|
||||
if let source = retrievingContext.options.lowDataModeSource {
|
||||
retrievingContext.options.lowDataModeSource = nil
|
||||
startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated)
|
||||
} else {
|
||||
// This should not happen.
|
||||
completionHandler?(.failure(error))
|
||||
}
|
||||
return
|
||||
}
|
||||
if let nextSource = retrievingContext.popAlternativeSource() {
|
||||
retrievingContext.appendError(error, to: source)
|
||||
startNewRetrieveTask(with: nextSource, downloadTaskUpdated: downloadTaskUpdated)
|
||||
} else {
|
||||
// No other alternative source. Finish with error.
|
||||
if retrievingContext.propagationErrors.isEmpty {
|
||||
completionHandler?(.failure(error))
|
||||
} else {
|
||||
retrievingContext.appendError(error, to: source)
|
||||
let finalError = KingfisherError.imageSettingError(
|
||||
reason: .alternativeSourcesExhausted(retrievingContext.propagationErrors)
|
||||
)
|
||||
completionHandler?(.failure(finalError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handler(currentSource: Source, result: (Result<RetrieveImageResult, KingfisherError>)) -> Void {
|
||||
switch result {
|
||||
case .success:
|
||||
completionHandler?(result)
|
||||
case .failure(let error):
|
||||
if let retryStrategy = options.retryStrategy {
|
||||
let context = retryContext?.increaseRetryCount() ?? RetryContext(source: source, error: error)
|
||||
retryContext = context
|
||||
|
||||
retryStrategy.retry(context: context) { decision in
|
||||
switch decision {
|
||||
case .retry(let userInfo):
|
||||
retryContext?.userInfo = userInfo
|
||||
startNewRetrieveTask(with: source, downloadTaskUpdated: downloadTaskUpdated)
|
||||
case .stop:
|
||||
failCurrentSource(currentSource, with: error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
failCurrentSource(currentSource, with: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retrieveImage(
|
||||
with: source,
|
||||
context: retrievingContext)
|
||||
{
|
||||
result in
|
||||
handler(currentSource: source, result: result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func retrieveImage(
|
||||
with source: Source,
|
||||
context: RetrievingContext,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
|
||||
{
|
||||
let options = context.options
|
||||
if options.forceRefresh {
|
||||
return loadAndCacheImage(
|
||||
source: source,
|
||||
context: context,
|
||||
completionHandler: completionHandler)?.value
|
||||
|
||||
} else {
|
||||
let loadedFromCache = retrieveImageFromCache(
|
||||
source: source,
|
||||
context: context,
|
||||
completionHandler: completionHandler)
|
||||
|
||||
if loadedFromCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.onlyFromCache {
|
||||
let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey))
|
||||
completionHandler?(.failure(error))
|
||||
return nil
|
||||
}
|
||||
|
||||
return loadAndCacheImage(
|
||||
source: source,
|
||||
context: context,
|
||||
completionHandler: completionHandler)?.value
|
||||
}
|
||||
}
|
||||
|
||||
func provideImage(
|
||||
provider: ImageDataProvider,
|
||||
options: KingfisherParsedOptionsInfo,
|
||||
completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)?)
|
||||
{
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
provider.data { result in
|
||||
switch result {
|
||||
case .success(let data):
|
||||
(options.processingQueue ?? self.processingQueue).execute {
|
||||
let processor = options.processor
|
||||
let processingItem = ImageProcessItem.data(data)
|
||||
guard let image = processor.process(item: processingItem, options: options) else {
|
||||
options.callbackQueue.execute {
|
||||
let error = KingfisherError.processorError(
|
||||
reason: .processingFailed(processor: processor, item: processingItem))
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
options.callbackQueue.execute {
|
||||
let result = ImageLoadingResult(image: image, url: nil, originalData: data)
|
||||
completionHandler(.success(result))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
options.callbackQueue.execute {
|
||||
let error = KingfisherError.imageSettingError(
|
||||
reason: .dataProviderError(provider: provider, error: error))
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cacheImage(
|
||||
source: Source,
|
||||
options: KingfisherParsedOptionsInfo,
|
||||
context: RetrievingContext,
|
||||
result: Result<ImageLoadingResult, KingfisherError>,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?
|
||||
)
|
||||
{
|
||||
switch result {
|
||||
case .success(let value):
|
||||
let needToCacheOriginalImage = options.cacheOriginalImage &&
|
||||
options.processor != DefaultImageProcessor.default
|
||||
let coordinator = CacheCallbackCoordinator(
|
||||
shouldWaitForCache: options.waitForCache, shouldCacheOriginal: needToCacheOriginalImage)
|
||||
let result = RetrieveImageResult(
|
||||
image: options.imageModifier?.modify(value.image) ?? value.image,
|
||||
cacheType: .none,
|
||||
source: source,
|
||||
originalSource: context.originalSource,
|
||||
data: { value.originalData }
|
||||
)
|
||||
// Add image to cache.
|
||||
let targetCache = options.targetCache ?? self.cache
|
||||
targetCache.store(
|
||||
value.image,
|
||||
original: value.originalData,
|
||||
forKey: source.cacheKey,
|
||||
options: options,
|
||||
toDisk: !options.cacheMemoryOnly)
|
||||
{
|
||||
_ in
|
||||
coordinator.apply(.cachingImage) {
|
||||
completionHandler?(.success(result))
|
||||
}
|
||||
}
|
||||
|
||||
// Add original image to cache if necessary.
|
||||
|
||||
if needToCacheOriginalImage {
|
||||
let originalCache = options.originalCache ?? targetCache
|
||||
originalCache.storeToDisk(
|
||||
value.originalData,
|
||||
forKey: source.cacheKey,
|
||||
processorIdentifier: DefaultImageProcessor.default.identifier,
|
||||
expiration: options.diskCacheExpiration)
|
||||
{
|
||||
_ in
|
||||
coordinator.apply(.cachingOriginalImage) {
|
||||
completionHandler?(.success(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coordinator.apply(.cacheInitiated) {
|
||||
completionHandler?(.success(result))
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
completionHandler?(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func loadAndCacheImage(
|
||||
source: Source,
|
||||
context: RetrievingContext,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask.WrappedTask?
|
||||
{
|
||||
let options = context.options
|
||||
func _cacheImage(_ result: Result<ImageLoadingResult, KingfisherError>) {
|
||||
cacheImage(
|
||||
source: source,
|
||||
options: options,
|
||||
context: context,
|
||||
result: result,
|
||||
completionHandler: completionHandler
|
||||
)
|
||||
}
|
||||
|
||||
switch source {
|
||||
case .network(let resource):
|
||||
let downloader = options.downloader ?? self.downloader
|
||||
let task = downloader.downloadImage(
|
||||
with: resource.downloadURL, options: options, completionHandler: _cacheImage
|
||||
)
|
||||
|
||||
|
||||
// The code below is neat, but it fails the Swift 5.2 compiler with a runtime crash when
|
||||
// `BUILD_LIBRARY_FOR_DISTRIBUTION` is turned on. I believe it is a bug in the compiler.
|
||||
// Let's fallback to a traditional style before it can be fixed in Swift.
|
||||
//
|
||||
// https://github.com/onevcat/Kingfisher/issues/1436
|
||||
//
|
||||
// return task.map(DownloadTask.WrappedTask.download)
|
||||
|
||||
if let task = task {
|
||||
return .download(task)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
case .provider(let provider):
|
||||
provideImage(provider: provider, options: options, completionHandler: _cacheImage)
|
||||
return .dataProviding
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves image from memory or disk cache.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - source: The target source from which to get image.
|
||||
/// - key: The key to use when caching the image.
|
||||
/// - url: Image request URL. This is not used when retrieving image from cache. It is just used for
|
||||
/// `RetrieveImageResult` callback compatibility.
|
||||
/// - options: Options on how to get the image from image cache.
|
||||
/// - completionHandler: Called when the image retrieving finishes, either with succeeded
|
||||
/// `RetrieveImageResult` or an error.
|
||||
/// - Returns: `true` if the requested image or the original image before being processed is existing in cache.
|
||||
/// Otherwise, this method returns `false`.
|
||||
///
|
||||
/// - Note:
|
||||
/// The image retrieving could happen in either memory cache or disk cache. The `.processor` option in
|
||||
/// `options` will be considered when searching in the cache. If no processed image is found, Kingfisher
|
||||
/// will try to check whether an original version of that image is existing or not. If there is already an
|
||||
/// original, Kingfisher retrieves it from cache and processes it. Then, the processed image will be store
|
||||
/// back to cache for later use.
|
||||
func retrieveImageFromCache(
|
||||
source: Source,
|
||||
context: RetrievingContext,
|
||||
completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> Bool
|
||||
{
|
||||
let options = context.options
|
||||
// 1. Check whether the image was already in target cache. If so, just get it.
|
||||
let targetCache = options.targetCache ?? cache
|
||||
let key = source.cacheKey
|
||||
let targetImageCached = targetCache.imageCachedType(
|
||||
forKey: key, processorIdentifier: options.processor.identifier)
|
||||
|
||||
let validCache = targetImageCached.cached &&
|
||||
(options.fromMemoryCacheOrRefresh == false || targetImageCached == .memory)
|
||||
if validCache {
|
||||
targetCache.retrieveImage(forKey: key, options: options) { result in
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
|
||||
// TODO: Optimize it when we can use async across all the project.
|
||||
func checkResultImageAndCallback(_ inputImage: KFCrossPlatformImage) {
|
||||
var image = inputImage
|
||||
if image.kf.imageFrameCount != nil && image.kf.imageFrameCount != 1, let data = image.kf.animatedImageData {
|
||||
// Always recreate animated image representation since it is possible to be loaded in different options.
|
||||
// https://github.com/onevcat/Kingfisher/issues/1923
|
||||
image = options.processor.process(item: .data(data), options: options) ?? .init()
|
||||
}
|
||||
if let modifier = options.imageModifier {
|
||||
image = modifier.modify(image)
|
||||
}
|
||||
let value = result.map {
|
||||
RetrieveImageResult(
|
||||
image: image,
|
||||
cacheType: $0.cacheType,
|
||||
source: source,
|
||||
originalSource: context.originalSource,
|
||||
data: { options.cacheSerializer.data(with: image, original: nil) }
|
||||
)
|
||||
}
|
||||
completionHandler(value)
|
||||
}
|
||||
|
||||
result.match { cacheResult in
|
||||
options.callbackQueue.execute {
|
||||
guard let image = cacheResult.image else {
|
||||
completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
|
||||
return
|
||||
}
|
||||
|
||||
if options.cacheSerializer.originalDataUsed {
|
||||
let processor = options.processor
|
||||
(options.processingQueue ?? self.processingQueue).execute {
|
||||
let item = ImageProcessItem.image(image)
|
||||
guard let processedImage = processor.process(item: item, options: options) else {
|
||||
let error = KingfisherError.processorError(
|
||||
reason: .processingFailed(processor: processor, item: item))
|
||||
options.callbackQueue.execute { completionHandler(.failure(error)) }
|
||||
return
|
||||
}
|
||||
options.callbackQueue.execute {
|
||||
checkResultImageAndCallback(processedImage)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkResultImageAndCallback(image)
|
||||
}
|
||||
}
|
||||
} onFailure: { error in
|
||||
options.callbackQueue.execute {
|
||||
completionHandler(.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key))))
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 2. Check whether the original image exists. If so, get it, process it, save to storage and return.
|
||||
let originalCache = options.originalCache ?? targetCache
|
||||
// No need to store the same file in the same cache again.
|
||||
if originalCache === targetCache && options.processor == DefaultImageProcessor.default {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the unprocessed image existing or not.
|
||||
let originalImageCacheType = originalCache.imageCachedType(
|
||||
forKey: key, processorIdentifier: DefaultImageProcessor.default.identifier)
|
||||
let canAcceptDiskCache = !options.fromMemoryCacheOrRefresh
|
||||
|
||||
let canUseOriginalImageCache =
|
||||
(canAcceptDiskCache && originalImageCacheType.cached) ||
|
||||
(!canAcceptDiskCache && originalImageCacheType == .memory)
|
||||
|
||||
if canUseOriginalImageCache {
|
||||
// Now we are ready to get found the original image from cache. We need the unprocessed image, so remove
|
||||
// any processor from options first.
|
||||
var optionsWithoutProcessor = options
|
||||
optionsWithoutProcessor.processor = DefaultImageProcessor.default
|
||||
originalCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { result in
|
||||
|
||||
result.match(
|
||||
onSuccess: { cacheResult in
|
||||
guard let image = cacheResult.image else {
|
||||
assertionFailure("The image (under key: \(key) should be existing in the original cache.")
|
||||
return
|
||||
}
|
||||
|
||||
let processor = options.processor
|
||||
(options.processingQueue ?? self.processingQueue).execute {
|
||||
let item = ImageProcessItem.image(image)
|
||||
guard let processedImage = processor.process(item: item, options: options) else {
|
||||
let error = KingfisherError.processorError(
|
||||
reason: .processingFailed(processor: processor, item: item))
|
||||
options.callbackQueue.execute { completionHandler?(.failure(error)) }
|
||||
return
|
||||
}
|
||||
|
||||
var cacheOptions = options
|
||||
cacheOptions.callbackQueue = .untouch
|
||||
|
||||
let coordinator = CacheCallbackCoordinator(
|
||||
shouldWaitForCache: options.waitForCache, shouldCacheOriginal: false)
|
||||
|
||||
let image = options.imageModifier?.modify(processedImage) ?? processedImage
|
||||
let result = RetrieveImageResult(
|
||||
image: image,
|
||||
cacheType: .none,
|
||||
source: source,
|
||||
originalSource: context.originalSource,
|
||||
data: { options.cacheSerializer.data(with: processedImage, original: nil) }
|
||||
)
|
||||
|
||||
targetCache.store(
|
||||
processedImage,
|
||||
forKey: key,
|
||||
options: cacheOptions,
|
||||
toDisk: !options.cacheMemoryOnly)
|
||||
{
|
||||
_ in
|
||||
coordinator.apply(.cachingImage) {
|
||||
options.callbackQueue.execute { completionHandler?(.success(result)) }
|
||||
}
|
||||
}
|
||||
|
||||
coordinator.apply(.cacheInitiated) {
|
||||
options.callbackQueue.execute { completionHandler?(.success(result)) }
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailure: { _ in
|
||||
// This should not happen actually, since we already confirmed `originalImageCached` is `true`.
|
||||
// Just in case...
|
||||
options.callbackQueue.execute {
|
||||
completionHandler?(
|
||||
.failure(KingfisherError.cacheError(reason: .imageNotExisting(key: key)))
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class RetrievingContext {
|
||||
|
||||
var options: KingfisherParsedOptionsInfo
|
||||
|
||||
let originalSource: Source
|
||||
var propagationErrors: [PropagationError] = []
|
||||
|
||||
init(options: KingfisherParsedOptionsInfo, originalSource: Source) {
|
||||
self.originalSource = originalSource
|
||||
self.options = options
|
||||
}
|
||||
|
||||
func popAlternativeSource() -> Source? {
|
||||
guard var alternativeSources = options.alternativeSources, !alternativeSources.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
let nextSource = alternativeSources.removeFirst()
|
||||
options.alternativeSources = alternativeSources
|
||||
return nextSource
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func appendError(_ error: KingfisherError, to source: Source) -> [PropagationError] {
|
||||
let item = PropagationError(source: source, error: error)
|
||||
propagationErrors.append(item)
|
||||
return propagationErrors
|
||||
}
|
||||
}
|
||||
|
||||
class CacheCallbackCoordinator {
|
||||
|
||||
enum State {
|
||||
case idle
|
||||
case imageCached
|
||||
case originalImageCached
|
||||
case done
|
||||
}
|
||||
|
||||
enum Action {
|
||||
case cacheInitiated
|
||||
case cachingImage
|
||||
case cachingOriginalImage
|
||||
}
|
||||
|
||||
private let shouldWaitForCache: Bool
|
||||
private let shouldCacheOriginal: Bool
|
||||
private let stateQueue: DispatchQueue
|
||||
private var threadSafeState: State = .idle
|
||||
|
||||
private (set) var state: State {
|
||||
set { stateQueue.sync { threadSafeState = newValue } }
|
||||
get { stateQueue.sync { threadSafeState } }
|
||||
}
|
||||
|
||||
init(shouldWaitForCache: Bool, shouldCacheOriginal: Bool) {
|
||||
self.shouldWaitForCache = shouldWaitForCache
|
||||
self.shouldCacheOriginal = shouldCacheOriginal
|
||||
let stateQueueName = "com.onevcat.Kingfisher.CacheCallbackCoordinator.stateQueue.\(UUID().uuidString)"
|
||||
self.stateQueue = DispatchQueue(label: stateQueueName)
|
||||
}
|
||||
|
||||
func apply(_ action: Action, trigger: () -> Void) {
|
||||
switch (state, action) {
|
||||
case (.done, _):
|
||||
break
|
||||
|
||||
// From .idle
|
||||
case (.idle, .cacheInitiated):
|
||||
if !shouldWaitForCache {
|
||||
state = .done
|
||||
trigger()
|
||||
}
|
||||
case (.idle, .cachingImage):
|
||||
if shouldCacheOriginal {
|
||||
state = .imageCached
|
||||
} else {
|
||||
state = .done
|
||||
trigger()
|
||||
}
|
||||
case (.idle, .cachingOriginalImage):
|
||||
state = .originalImageCached
|
||||
|
||||
// From .imageCached
|
||||
case (.imageCached, .cachingOriginalImage):
|
||||
state = .done
|
||||
trigger()
|
||||
|
||||
// From .originalImageCached
|
||||
case (.originalImageCached, .cachingImage):
|
||||
state = .done
|
||||
trigger()
|
||||
|
||||
default:
|
||||
assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)")
|
||||
}
|
||||
}
|
||||
}
|
||||
400
Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift
generated
Normal file
400
Pods/Kingfisher/Sources/General/KingfisherOptionsInfo.swift
generated
Normal file
@@ -0,0 +1,400 @@
|
||||
//
|
||||
// KingfisherOptionsInfo.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 15/4/23.
|
||||
//
|
||||
// Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
|
||||
//
|
||||
// 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]
|
||||
|
||||
extension Array where Element == KingfisherOptionsInfoItem {
|
||||
static let empty: KingfisherOptionsInfo = []
|
||||
}
|
||||
|
||||
/// Represents the available option items could be used in `KingfisherOptionsInfo`.
|
||||
public enum KingfisherOptionsInfoItem {
|
||||
|
||||
/// Kingfisher will use the associated `ImageCache` object when handling related operations,
|
||||
/// including trying to retrieve the cached images and store the downloaded image to it.
|
||||
case targetCache(ImageCache)
|
||||
|
||||
/// The `ImageCache` for storing and retrieving original images. If `originalCache` is
|
||||
/// contained in the options, it will be preferred for storing and retrieving original images.
|
||||
/// If there is no `.originalCache` in the options, `.targetCache` will be used to store original images.
|
||||
///
|
||||
/// When using KingfisherManager to download and store an image, if `cacheOriginalImage` is
|
||||
/// applied in the option, the original image will be stored to this `originalCache`. At the
|
||||
/// same time, if a requested final image (with processor applied) cannot be found in `targetCache`,
|
||||
/// Kingfisher will try to search the original image to check whether it is already there. If found,
|
||||
/// it will be used and applied with the given processor. It is an optimization for not downloading
|
||||
/// the same image for multiple times.
|
||||
case originalCache(ImageCache)
|
||||
|
||||
/// Kingfisher will use the associated `ImageDownloader` object to download the requested 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 `.forceRefresh` 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 (`URLSessionTask.defaultPriority`) will be used.
|
||||
case downloadPriority(Float)
|
||||
|
||||
/// If set, Kingfisher will ignore the cache and try to start a download task for the image source.
|
||||
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 at the same app session, while avoiding download
|
||||
/// it for multiple times.
|
||||
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 wait for caching operation to be completed before calling the completion block.
|
||||
case waitForCache
|
||||
|
||||
/// If set, Kingfisher will only try to retrieve the image from cache, but not from network. If the image is not in
|
||||
/// cache, the image retrieving will fail with the `KingfisherError.cacheError` with `.imageNotExisting` as its
|
||||
/// reason.
|
||||
case onlyFromCache
|
||||
|
||||
/// Decode the image in background thread before using. It will decode the downloaded image data and do a off-screen
|
||||
/// rendering to extract pixel information in background. This can speed up display, but will cost more time to
|
||||
/// prepare the image for using.
|
||||
case backgroundDecode
|
||||
|
||||
/// The associated value will be used as the target queue of dispatch callbacks when retrieving images from
|
||||
/// cache. If not set, Kingfisher will use `.mainCurrentOrAsync` for callbacks.
|
||||
///
|
||||
/// - Note:
|
||||
/// This option does not affect the callbacks for UI related extension methods. You will always get the
|
||||
/// callbacks called from main queue.
|
||||
case callbackQueue(CallbackQueue)
|
||||
|
||||
/// The associated value will be used as the scale factor when converting retrieved data to an image.
|
||||
/// Specify the image scale, instead of your screen scale. You may need to set the correct scale when you dealing
|
||||
/// with 2x or 3x retina images. Otherwise, Kingfisher will convert the data to image object at `scale` 1.0.
|
||||
case scaleFactor(CGFloat)
|
||||
|
||||
/// Whether all the animated image data should be preloaded. Default is `false`, which means only 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. Instead,
|
||||
/// you should choose the image view class to control the GIF data loading. There are two classes in Kingfisher
|
||||
/// support to display a GIF image. `AnimatedImageView` does not preload all data, it takes much less memory, but
|
||||
/// uses more CPU when display. While a normal image view (`UIImageView` or `NSImageView`) loads all data at once,
|
||||
/// which uses more memory but only decode image frames once.
|
||||
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 image download 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(AsyncImageDownloadRequestModifier)
|
||||
|
||||
/// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection.
|
||||
/// This is the possibility you can modify the image download request during redirect. 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 redirection request will be sent without any modification by default.
|
||||
case redirectHandler(ImageDownloadRedirectHandler)
|
||||
|
||||
/// 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 any of the view extension methods), the converted image will also be sent to cache as well.
|
||||
/// If not set, the `DefaultImageProcessor.default` will be used.
|
||||
case processor(ImageProcessor)
|
||||
|
||||
/// Provides a `CacheSerializer` to convert some data to an image object for
|
||||
/// retrieving from disk cache or vice versa for storing to disk cache.
|
||||
/// If not set, the `DefaultCacheSerializer.default` will be used.
|
||||
case cacheSerializer(CacheSerializer)
|
||||
|
||||
/// An `ImageModifier` is for modifying an image as needed right before it is used. If the image was fetched
|
||||
/// directly from the downloader, the modifier will run directly after the `ImageProcessor`. If the image is being
|
||||
/// fetched from a cache, the modifier will run after the `CacheSerializer`.
|
||||
///
|
||||
/// Use `ImageModifier` when you need to set properties that do not persist when caching the image on a concrete
|
||||
/// type of `Image`, such as the `renderingMode` or the `alignmentInsets` of `UIImage`.
|
||||
case imageModifier(ImageModifier)
|
||||
|
||||
/// Keep the existing image of image view while setting another image to it.
|
||||
/// By setting this option, the placeholder image parameter of image view 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 an animated image file as a single image.
|
||||
/// Loading an animated images may take too much memory. It will be useful when you want to display a
|
||||
/// static preview of the first frame from an 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
|
||||
/// resource, instead of downloading it again. You can use `.originalCache` to specify a cache or the original
|
||||
/// images if necessary.
|
||||
///
|
||||
/// The original image will be only cached to disk storage.
|
||||
case cacheOriginalImage
|
||||
|
||||
/// If set and an image retrieving error occurred Kingfisher will set provided image (or empty)
|
||||
/// in place of requested one. It's useful when you don't want to show placeholder
|
||||
/// during loading time but wants to use some default image when requests will be failed.
|
||||
case onFailureImage(KFCrossPlatformImage?)
|
||||
|
||||
/// If set and used in `ImagePrefetcher`, the prefetching operation will load the images into memory storage
|
||||
/// aggressively. By default this is not contained in the options, that means if the requested image is already
|
||||
/// in disk cache, Kingfisher will not try to load it to memory.
|
||||
case alsoPrefetchToMemory
|
||||
|
||||
/// If set, the disk storage loading will happen in the same calling queue. By default, disk storage file loading
|
||||
/// happens in its own queue with an asynchronous dispatch behavior. Although it provides better non-blocking disk
|
||||
/// loading performance, it also causes a flickering when you reload an image from disk, if the image view already
|
||||
/// has an image set.
|
||||
///
|
||||
/// Set this options will stop that flickering by keeping all loading in the same queue (typically the UI queue
|
||||
/// if you are using Kingfisher's extension methods to set an image), with a tradeoff of loading performance.
|
||||
case loadDiskFileSynchronously
|
||||
|
||||
/// Options to control the writing of data to disk storage
|
||||
/// If set, options will be passed the store operation for a new files.
|
||||
case diskStoreWriteOptions(Data.WritingOptions)
|
||||
|
||||
/// The expiration setting for memory cache. By default, the underlying `MemoryStorage.Backend` uses the
|
||||
/// expiration in its config for all items. If set, the `MemoryStorage.Backend` will use this associated
|
||||
/// value to overwrite the config setting for this caching item.
|
||||
case memoryCacheExpiration(StorageExpiration)
|
||||
|
||||
/// The expiration extending setting for memory cache. The item expiration time will be incremented by this
|
||||
/// value after access.
|
||||
/// By default, the underlying `MemoryStorage.Backend` uses the initial cache expiration as extending
|
||||
/// value: .cacheTime.
|
||||
///
|
||||
/// To disable extending option at all add memoryCacheAccessExtendingExpiration(.none) to options.
|
||||
case memoryCacheAccessExtendingExpiration(ExpirationExtending)
|
||||
|
||||
/// The expiration setting for disk cache. By default, the underlying `DiskStorage.Backend` uses the
|
||||
/// expiration in its config for all items. If set, the `DiskStorage.Backend` will use this associated
|
||||
/// value to overwrite the config setting for this caching item.
|
||||
case diskCacheExpiration(StorageExpiration)
|
||||
|
||||
/// The expiration extending setting for disk cache. The item expiration time will be incremented by this value after access.
|
||||
/// By default, the underlying `DiskStorage.Backend` uses the initial cache expiration as extending value: .cacheTime.
|
||||
/// To disable extending option at all add diskCacheAccessExtendingExpiration(.none) to options.
|
||||
case diskCacheAccessExtendingExpiration(ExpirationExtending)
|
||||
|
||||
/// Decides on which queue the image processing should happen. By default, Kingfisher uses a pre-defined serial
|
||||
/// queue to process images. Use this option to change this behavior. For example, specify a `.mainCurrentOrAsync`
|
||||
/// to let the image be processed in main queue to prevent a possible flickering (but with a possibility of
|
||||
/// blocking the UI, especially if the processor needs a lot of time to run).
|
||||
case processingQueue(CallbackQueue)
|
||||
|
||||
/// Enable progressive image loading, Kingfisher will use the associated `ImageProgressive` value to process the
|
||||
/// progressive JPEG data and display it in a progressive way.
|
||||
case progressiveJPEG(ImageProgressive)
|
||||
|
||||
/// The alternative sources will be used when the original input `Source` fails. The `Source`s in the associated
|
||||
/// array will be used to start a new image loading task if the previous task fails due to an error. The image
|
||||
/// source loading process will stop as soon as a source is loaded successfully. If all `[Source]`s are used but
|
||||
/// the loading is still failing, an `imageSettingError` with `alternativeSourcesExhausted` as its reason will be
|
||||
/// thrown out.
|
||||
///
|
||||
/// This option is useful if you want to implement a fallback solution for setting image.
|
||||
///
|
||||
/// User cancellation will not trigger the alternative source loading.
|
||||
case alternativeSources([Source])
|
||||
|
||||
/// Provide a retry strategy which will be used when something gets wrong during the image retrieving process from
|
||||
/// `KingfisherManager`. You can define a strategy by create a type conforming to the `RetryStrategy` protocol.
|
||||
///
|
||||
/// - Note:
|
||||
///
|
||||
/// All extension methods of Kingfisher (`kf` extensions on `UIImageView` or `UIButton`) retrieve images through
|
||||
/// `KingfisherManager`, so the retry strategy also applies when using them. However, this option does not apply
|
||||
/// when pass to an `ImageDownloader` or `ImageCache`.
|
||||
///
|
||||
case retryStrategy(RetryStrategy)
|
||||
|
||||
/// The `Source` should be loaded when user enables Low Data Mode and the original source fails with an
|
||||
/// `NSURLErrorNetworkUnavailableReason.constrained` error. When this option is set, the
|
||||
/// `allowsConstrainedNetworkAccess` property of the request for the original source will be set to `false` and the
|
||||
/// `Source` in associated value will be used to retrieve the image for low data mode. Usually, you can provide a
|
||||
/// low-resolution version of your image or a local image provider to display a placeholder.
|
||||
///
|
||||
/// If not set or the `source` is `nil`, the device Low Data Mode will be ignored and the original source will
|
||||
/// be loaded following the system default behavior, in a normal way.
|
||||
case lowDataMode(Source?)
|
||||
}
|
||||
|
||||
// Improve performance by parsing the input `KingfisherOptionsInfo` (self) first.
|
||||
// So we can prevent the iterating over the options array again and again.
|
||||
/// The parsed options info used across Kingfisher methods. Each property in this type corresponds a case member
|
||||
/// in `KingfisherOptionsInfoItem`. When a `KingfisherOptionsInfo` sent to Kingfisher related methods, it will be
|
||||
/// parsed and converted to a `KingfisherParsedOptionsInfo` first, and pass through the internal methods.
|
||||
public struct KingfisherParsedOptionsInfo {
|
||||
|
||||
public var targetCache: ImageCache? = nil
|
||||
public var originalCache: ImageCache? = nil
|
||||
public var downloader: ImageDownloader? = nil
|
||||
public var transition: ImageTransition = .none
|
||||
public var downloadPriority: Float = URLSessionTask.defaultPriority
|
||||
public var forceRefresh = false
|
||||
public var fromMemoryCacheOrRefresh = false
|
||||
public var forceTransition = false
|
||||
public var cacheMemoryOnly = false
|
||||
public var waitForCache = false
|
||||
public var onlyFromCache = false
|
||||
public var backgroundDecode = false
|
||||
public var preloadAllAnimationData = false
|
||||
public var callbackQueue: CallbackQueue = .mainCurrentOrAsync
|
||||
public var scaleFactor: CGFloat = 1.0
|
||||
public var requestModifier: AsyncImageDownloadRequestModifier? = nil
|
||||
public var redirectHandler: ImageDownloadRedirectHandler? = nil
|
||||
public var processor: ImageProcessor = DefaultImageProcessor.default
|
||||
public var imageModifier: ImageModifier? = nil
|
||||
public var cacheSerializer: CacheSerializer = DefaultCacheSerializer.default
|
||||
public var keepCurrentImageWhileLoading = false
|
||||
public var onlyLoadFirstFrame = false
|
||||
public var cacheOriginalImage = false
|
||||
public var onFailureImage: Optional<KFCrossPlatformImage?> = .none
|
||||
public var alsoPrefetchToMemory = false
|
||||
public var loadDiskFileSynchronously = false
|
||||
public var diskStoreWriteOptions: Data.WritingOptions = []
|
||||
public var memoryCacheExpiration: StorageExpiration? = nil
|
||||
public var memoryCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
|
||||
public var diskCacheExpiration: StorageExpiration? = nil
|
||||
public var diskCacheAccessExtendingExpiration: ExpirationExtending = .cacheTime
|
||||
public var processingQueue: CallbackQueue? = nil
|
||||
public var progressiveJPEG: ImageProgressive? = nil
|
||||
public var alternativeSources: [Source]? = nil
|
||||
public var retryStrategy: RetryStrategy? = nil
|
||||
public var lowDataModeSource: Source? = nil
|
||||
|
||||
var onDataReceived: [DataReceivingSideEffect]? = nil
|
||||
|
||||
public init(_ info: KingfisherOptionsInfo?) {
|
||||
guard let info = info else { return }
|
||||
for option in info {
|
||||
switch option {
|
||||
case .targetCache(let value): targetCache = value
|
||||
case .originalCache(let value): originalCache = value
|
||||
case .downloader(let value): downloader = value
|
||||
case .transition(let value): transition = value
|
||||
case .downloadPriority(let value): downloadPriority = value
|
||||
case .forceRefresh: forceRefresh = true
|
||||
case .fromMemoryCacheOrRefresh: fromMemoryCacheOrRefresh = true
|
||||
case .forceTransition: forceTransition = true
|
||||
case .cacheMemoryOnly: cacheMemoryOnly = true
|
||||
case .waitForCache: waitForCache = true
|
||||
case .onlyFromCache: onlyFromCache = true
|
||||
case .backgroundDecode: backgroundDecode = true
|
||||
case .preloadAllAnimationData: preloadAllAnimationData = true
|
||||
case .callbackQueue(let value): callbackQueue = value
|
||||
case .scaleFactor(let value): scaleFactor = value
|
||||
case .requestModifier(let value): requestModifier = value
|
||||
case .redirectHandler(let value): redirectHandler = value
|
||||
case .processor(let value): processor = value
|
||||
case .imageModifier(let value): imageModifier = value
|
||||
case .cacheSerializer(let value): cacheSerializer = value
|
||||
case .keepCurrentImageWhileLoading: keepCurrentImageWhileLoading = true
|
||||
case .onlyLoadFirstFrame: onlyLoadFirstFrame = true
|
||||
case .cacheOriginalImage: cacheOriginalImage = true
|
||||
case .onFailureImage(let value): onFailureImage = .some(value)
|
||||
case .alsoPrefetchToMemory: alsoPrefetchToMemory = true
|
||||
case .loadDiskFileSynchronously: loadDiskFileSynchronously = true
|
||||
case .diskStoreWriteOptions(let options): diskStoreWriteOptions = options
|
||||
case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration
|
||||
case .memoryCacheAccessExtendingExpiration(let expirationExtending): memoryCacheAccessExtendingExpiration = expirationExtending
|
||||
case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration
|
||||
case .diskCacheAccessExtendingExpiration(let expirationExtending): diskCacheAccessExtendingExpiration = expirationExtending
|
||||
case .processingQueue(let queue): processingQueue = queue
|
||||
case .progressiveJPEG(let value): progressiveJPEG = value
|
||||
case .alternativeSources(let sources): alternativeSources = sources
|
||||
case .retryStrategy(let strategy): retryStrategy = strategy
|
||||
case .lowDataMode(let source): lowDataModeSource = source
|
||||
}
|
||||
}
|
||||
|
||||
if originalCache == nil {
|
||||
originalCache = targetCache
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherParsedOptionsInfo {
|
||||
var imageCreatingOptions: ImageCreatingOptions {
|
||||
return ImageCreatingOptions(
|
||||
scale: scaleFactor,
|
||||
duration: 0.0,
|
||||
preloadAll: preloadAllAnimationData,
|
||||
onlyFirstFrame: onlyLoadFirstFrame)
|
||||
}
|
||||
}
|
||||
|
||||
protocol DataReceivingSideEffect: AnyObject {
|
||||
var onShouldApply: () -> Bool { get set }
|
||||
func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data)
|
||||
}
|
||||
|
||||
class ImageLoadingProgressSideEffect: DataReceivingSideEffect {
|
||||
|
||||
var onShouldApply: () -> Bool = { return true }
|
||||
|
||||
let block: DownloadProgressBlock
|
||||
|
||||
init(_ block: @escaping DownloadProgressBlock) {
|
||||
self.block = block
|
||||
}
|
||||
|
||||
func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) {
|
||||
guard self.onShouldApply() else { return }
|
||||
guard let expectedContentLength = task.task.response?.expectedContentLength,
|
||||
expectedContentLength != -1 else
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
let dataLength = Int64(task.mutableData.count)
|
||||
DispatchQueue.main.async {
|
||||
self.block(dataLength, expectedContentLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user