update
This commit is contained in:
94
Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift
generated
Normal file
94
Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift
generated
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// AuthenticationChallengeResponsable.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2018/10/11.
|
||||
//
|
||||
// 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
|
||||
|
||||
@available(*, deprecated, message: "Typo. Use `AuthenticationChallengeResponsible` instead", renamed: "AuthenticationChallengeResponsible")
|
||||
public typealias AuthenticationChallengeResponsable = AuthenticationChallengeResponsible
|
||||
|
||||
/// Protocol indicates that an authentication challenge could be handled.
|
||||
public protocol AuthenticationChallengeResponsible: AnyObject {
|
||||
|
||||
/// Called when a session level authentication challenge is received.
|
||||
/// This method provide a chance to handle and response to the authentication
|
||||
/// challenge before downloading could start.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The downloader which receives this challenge.
|
||||
/// - challenge: An object that contains the request for authentication.
|
||||
/// - completionHandler: A handler that your delegate method must call.
|
||||
///
|
||||
/// - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`.
|
||||
/// Please refer to the document of it in `URLSessionDelegate`.
|
||||
func downloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didReceive challenge: URLAuthenticationChallenge,
|
||||
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
||||
|
||||
/// Called when a task level authentication challenge is received.
|
||||
/// This method provide a chance to handle and response to the authentication
|
||||
/// challenge before downloading could start.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The downloader which receives this challenge.
|
||||
/// - task: The task whose request requires authentication.
|
||||
/// - challenge: An object that contains the request for authentication.
|
||||
/// - completionHandler: A handler that your delegate method must call.
|
||||
func downloader(
|
||||
_ downloader: ImageDownloader,
|
||||
task: URLSessionTask,
|
||||
didReceive challenge: URLAuthenticationChallenge,
|
||||
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
||||
}
|
||||
|
||||
extension AuthenticationChallengeResponsible {
|
||||
|
||||
public func downloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didReceive challenge: URLAuthenticationChallenge,
|
||||
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
||||
{
|
||||
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
|
||||
if let trustedHosts = downloader.trustedHosts, trustedHosts.contains(challenge.protectionSpace.host) {
|
||||
let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
|
||||
completionHandler(.useCredential, credential)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler(.performDefaultHandling, nil)
|
||||
}
|
||||
|
||||
public func downloader(
|
||||
_ downloader: ImageDownloader,
|
||||
task: URLSessionTask,
|
||||
didReceive challenge: URLAuthenticationChallenge,
|
||||
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
||||
{
|
||||
completionHandler(.performDefaultHandling, nil)
|
||||
}
|
||||
|
||||
}
|
||||
74
Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift
generated
Normal file
74
Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift
generated
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// ImageDataProcessor.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2018/10/11.
|
||||
//
|
||||
// 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
|
||||
|
||||
private let sharedProcessingQueue: CallbackQueue =
|
||||
.dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process"))
|
||||
|
||||
// Handles image processing work on an own process queue.
|
||||
class ImageDataProcessor {
|
||||
let data: Data
|
||||
let callbacks: [SessionDataTask.TaskCallback]
|
||||
let queue: CallbackQueue
|
||||
|
||||
// Note: We have an optimization choice there, to reduce queue dispatch by checking callback
|
||||
// queue settings in each option...
|
||||
let onImageProcessed = Delegate<(Result<KFCrossPlatformImage, KingfisherError>, SessionDataTask.TaskCallback), Void>()
|
||||
|
||||
init(data: Data, callbacks: [SessionDataTask.TaskCallback], processingQueue: CallbackQueue?) {
|
||||
self.data = data
|
||||
self.callbacks = callbacks
|
||||
self.queue = processingQueue ?? sharedProcessingQueue
|
||||
}
|
||||
|
||||
func process() {
|
||||
queue.execute(doProcess)
|
||||
}
|
||||
|
||||
private func doProcess() {
|
||||
var processedImages = [String: KFCrossPlatformImage]()
|
||||
for callback in callbacks {
|
||||
let processor = callback.options.processor
|
||||
var image = processedImages[processor.identifier]
|
||||
if image == nil {
|
||||
image = processor.process(item: .data(data), options: callback.options)
|
||||
processedImages[processor.identifier] = image
|
||||
}
|
||||
|
||||
let result: Result<KFCrossPlatformImage, KingfisherError>
|
||||
if let image = image {
|
||||
let finalImage = callback.options.backgroundDecode ? image.kf.decoded : image
|
||||
result = .success(finalImage)
|
||||
} else {
|
||||
let error = KingfisherError.processorError(
|
||||
reason: .processingFailed(processor: processor, item: .data(data)))
|
||||
result = .failure(error)
|
||||
}
|
||||
onImageProcessed.call((result, callback))
|
||||
}
|
||||
}
|
||||
}
|
||||
502
Pods/Kingfisher/Sources/Networking/ImageDownloader.swift
generated
Normal file
502
Pods/Kingfisher/Sources/Networking/ImageDownloader.swift
generated
Normal file
@@ -0,0 +1,502 @@
|
||||
//
|
||||
// ImageDownloader.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.
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
typealias DownloadResult = Result<ImageLoadingResult, KingfisherError>
|
||||
|
||||
/// Represents a success result of an image downloading progress.
|
||||
public struct ImageLoadingResult {
|
||||
|
||||
/// The downloaded image.
|
||||
public let image: KFCrossPlatformImage
|
||||
|
||||
/// Original URL of the image request.
|
||||
public let url: URL?
|
||||
|
||||
/// The raw data received from downloader.
|
||||
public let originalData: Data
|
||||
|
||||
/// Creates an `ImageDownloadResult`
|
||||
///
|
||||
/// - parameter image: Image of the download result
|
||||
/// - parameter url: URL from where the image was downloaded from
|
||||
/// - parameter originalData: The image's binary data
|
||||
public init(image: KFCrossPlatformImage, url: URL? = nil, originalData: Data) {
|
||||
self.image = image
|
||||
self.url = url
|
||||
self.originalData = originalData
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a task of an image downloading process.
|
||||
public struct DownloadTask {
|
||||
|
||||
/// The `SessionDataTask` object bounded to this download task. Multiple `DownloadTask`s could refer
|
||||
/// to a same `sessionTask`. This is an optimization in Kingfisher to prevent multiple downloading task
|
||||
/// for the same URL resource at the same time.
|
||||
///
|
||||
/// When you `cancel` a `DownloadTask`, this `SessionDataTask` and its cancel token will be pass through.
|
||||
/// You can use them to identify the cancelled task.
|
||||
public let sessionTask: SessionDataTask
|
||||
|
||||
/// The cancel token which is used to cancel the task. This is only for identify the task when it is cancelled.
|
||||
/// To cancel a `DownloadTask`, use `cancel` instead.
|
||||
public let cancelToken: SessionDataTask.CancelToken
|
||||
|
||||
/// Cancel this task if it is running. It will do nothing if this task is not running.
|
||||
///
|
||||
/// - Note:
|
||||
/// In Kingfisher, there is an optimization to prevent starting another download task if the target URL is being
|
||||
/// downloading. However, even when internally no new session task created, a `DownloadTask` will be still created
|
||||
/// and returned when you call related methods, but it will share the session downloading task with a previous task.
|
||||
/// In this case, if multiple `DownloadTask`s share a single session download task, cancelling a `DownloadTask`
|
||||
/// does not affect other `DownloadTask`s.
|
||||
///
|
||||
/// If you need to cancel all `DownloadTask`s of a url, use `ImageDownloader.cancel(url:)`. If you need to cancel
|
||||
/// all downloading tasks of an `ImageDownloader`, use `ImageDownloader.cancelAll()`.
|
||||
public func cancel() {
|
||||
sessionTask.cancel(token: cancelToken)
|
||||
}
|
||||
}
|
||||
|
||||
extension DownloadTask {
|
||||
enum WrappedTask {
|
||||
case download(DownloadTask)
|
||||
case dataProviding
|
||||
|
||||
func cancel() {
|
||||
switch self {
|
||||
case .download(let task): task.cancel()
|
||||
case .dataProviding: break
|
||||
}
|
||||
}
|
||||
|
||||
var value: DownloadTask? {
|
||||
switch self {
|
||||
case .download(let task): return task
|
||||
case .dataProviding: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a downloading manager for requesting the image with a URL from server.
|
||||
open class ImageDownloader {
|
||||
|
||||
// MARK: Singleton
|
||||
/// The default downloader.
|
||||
public static let `default` = ImageDownloader(name: "default")
|
||||
|
||||
// MARK: Public Properties
|
||||
/// The duration before the downloading is timeout. Default is 15 seconds.
|
||||
open var downloadTimeout: TimeInterval = 15.0
|
||||
|
||||
/// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this
|
||||
/// set will be ignored. You can use this set to specify the self-signed site. It only will be used if you don't
|
||||
/// specify the `authenticationChallengeResponder`.
|
||||
///
|
||||
/// If `authenticationChallengeResponder` is set, this property will be ignored and the implementation of
|
||||
/// `authenticationChallengeResponder` will be used instead.
|
||||
open var trustedHosts: Set<String>?
|
||||
|
||||
/// Use this to set supply a configuration for the downloader. By default,
|
||||
/// NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used.
|
||||
///
|
||||
/// You could change the configuration before a downloading task starts.
|
||||
/// A configuration without persistent storage for caches is requested for downloader working correctly.
|
||||
open var sessionConfiguration = URLSessionConfiguration.ephemeral {
|
||||
didSet {
|
||||
session.invalidateAndCancel()
|
||||
session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
|
||||
}
|
||||
}
|
||||
open var sessionDelegate: SessionDelegate {
|
||||
didSet {
|
||||
session.invalidateAndCancel()
|
||||
session = URLSession(configuration: sessionConfiguration, delegate: sessionDelegate, delegateQueue: nil)
|
||||
setupSessionHandler()
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the download requests should use pipeline or not. Default is false.
|
||||
open var requestsUsePipelining = false
|
||||
|
||||
/// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more.
|
||||
open weak var delegate: ImageDownloaderDelegate?
|
||||
|
||||
/// A responder for authentication challenge.
|
||||
/// Downloader will forward the received authentication challenge for the downloading session to this responder.
|
||||
open weak var authenticationChallengeResponder: AuthenticationChallengeResponsible?
|
||||
|
||||
private let name: String
|
||||
private var session: URLSession
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates a downloader with name.
|
||||
///
|
||||
/// - Parameter name: The name for the downloader. It should not be empty.
|
||||
public init(name: String) {
|
||||
if name.isEmpty {
|
||||
fatalError("[Kingfisher] You should specify a name for the downloader. "
|
||||
+ "A downloader with empty name is not permitted.")
|
||||
}
|
||||
|
||||
self.name = name
|
||||
|
||||
sessionDelegate = SessionDelegate()
|
||||
session = URLSession(
|
||||
configuration: sessionConfiguration,
|
||||
delegate: sessionDelegate,
|
||||
delegateQueue: nil)
|
||||
|
||||
authenticationChallengeResponder = self
|
||||
setupSessionHandler()
|
||||
}
|
||||
|
||||
deinit { session.invalidateAndCancel() }
|
||||
|
||||
private func setupSessionHandler() {
|
||||
sessionDelegate.onReceiveSessionChallenge.delegate(on: self) { (self, invoke) in
|
||||
self.authenticationChallengeResponder?.downloader(self, didReceive: invoke.1, completionHandler: invoke.2)
|
||||
}
|
||||
sessionDelegate.onReceiveSessionTaskChallenge.delegate(on: self) { (self, invoke) in
|
||||
self.authenticationChallengeResponder?.downloader(
|
||||
self, task: invoke.1, didReceive: invoke.2, completionHandler: invoke.3)
|
||||
}
|
||||
sessionDelegate.onValidStatusCode.delegate(on: self) { (self, code) in
|
||||
return (self.delegate ?? self).isValidStatusCode(code, for: self)
|
||||
}
|
||||
sessionDelegate.onResponseReceived.delegate(on: self) { (self, invoke) in
|
||||
(self.delegate ?? self).imageDownloader(self, didReceive: invoke.0, completionHandler: invoke.1)
|
||||
}
|
||||
sessionDelegate.onDownloadingFinished.delegate(on: self) { (self, value) in
|
||||
let (url, result) = value
|
||||
do {
|
||||
let value = try result.get()
|
||||
self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: value, error: nil)
|
||||
} catch {
|
||||
self.delegate?.imageDownloader(self, didFinishDownloadingImageForURL: url, with: nil, error: error)
|
||||
}
|
||||
}
|
||||
sessionDelegate.onDidDownloadData.delegate(on: self) { (self, task) in
|
||||
return (self.delegate ?? self).imageDownloader(self, didDownload: task.mutableData, with: task)
|
||||
}
|
||||
}
|
||||
|
||||
// Wraps `completionHandler` to `onCompleted` respectively.
|
||||
private func createCompletionCallBack(_ completionHandler: ((DownloadResult) -> Void)?) -> Delegate<DownloadResult, Void>? {
|
||||
return completionHandler.map { block -> Delegate<DownloadResult, Void> in
|
||||
|
||||
let delegate = Delegate<Result<ImageLoadingResult, KingfisherError>, Void>()
|
||||
delegate.delegate(on: self) { (self, callback) in
|
||||
block(callback)
|
||||
}
|
||||
return delegate
|
||||
}
|
||||
}
|
||||
|
||||
private func createTaskCallback(
|
||||
_ completionHandler: ((DownloadResult) -> Void)?,
|
||||
options: KingfisherParsedOptionsInfo
|
||||
) -> SessionDataTask.TaskCallback
|
||||
{
|
||||
return SessionDataTask.TaskCallback(
|
||||
onCompleted: createCompletionCallBack(completionHandler),
|
||||
options: options
|
||||
)
|
||||
}
|
||||
|
||||
private func createDownloadContext(
|
||||
with url: URL,
|
||||
options: KingfisherParsedOptionsInfo,
|
||||
done: @escaping ((Result<DownloadingContext, KingfisherError>) -> Void)
|
||||
)
|
||||
{
|
||||
func checkRequestAndDone(r: URLRequest) {
|
||||
|
||||
// There is a possibility that request modifier changed the url to `nil` or empty.
|
||||
// In this case, throw an error.
|
||||
guard let url = r.url, !url.absoluteString.isEmpty else {
|
||||
done(.failure(KingfisherError.requestError(reason: .invalidURL(request: r))))
|
||||
return
|
||||
}
|
||||
|
||||
done(.success(DownloadingContext(url: url, request: r, options: options)))
|
||||
}
|
||||
|
||||
// Creates default request.
|
||||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
|
||||
request.httpShouldUsePipelining = requestsUsePipelining
|
||||
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) , options.lowDataModeSource != nil {
|
||||
request.allowsConstrainedNetworkAccess = false
|
||||
}
|
||||
|
||||
if let requestModifier = options.requestModifier {
|
||||
// Modifies request before sending.
|
||||
requestModifier.modified(for: request) { result in
|
||||
guard let finalRequest = result else {
|
||||
done(.failure(KingfisherError.requestError(reason: .emptyRequest)))
|
||||
return
|
||||
}
|
||||
checkRequestAndDone(r: finalRequest)
|
||||
}
|
||||
} else {
|
||||
checkRequestAndDone(r: request)
|
||||
}
|
||||
}
|
||||
|
||||
private func addDownloadTask(
|
||||
context: DownloadingContext,
|
||||
callback: SessionDataTask.TaskCallback
|
||||
) -> DownloadTask
|
||||
{
|
||||
// Ready to start download. Add it to session task manager (`sessionHandler`)
|
||||
let downloadTask: DownloadTask
|
||||
if let existingTask = sessionDelegate.task(for: context.url) {
|
||||
downloadTask = sessionDelegate.append(existingTask, callback: callback)
|
||||
} else {
|
||||
let sessionDataTask = session.dataTask(with: context.request)
|
||||
sessionDataTask.priority = context.options.downloadPriority
|
||||
downloadTask = sessionDelegate.add(sessionDataTask, url: context.url, callback: callback)
|
||||
}
|
||||
return downloadTask
|
||||
}
|
||||
|
||||
|
||||
private func reportWillDownloadImage(url: URL, request: URLRequest) {
|
||||
delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request)
|
||||
}
|
||||
|
||||
private func reportDidDownloadImageData(result: Result<(Data, URLResponse?), KingfisherError>, url: URL) {
|
||||
var response: URLResponse?
|
||||
var err: Error?
|
||||
do {
|
||||
response = try result.get().1
|
||||
} catch {
|
||||
err = error
|
||||
}
|
||||
self.delegate?.imageDownloader(
|
||||
self,
|
||||
didFinishDownloadingImageForURL: url,
|
||||
with: response,
|
||||
error: err
|
||||
)
|
||||
}
|
||||
|
||||
private func reportDidProcessImage(
|
||||
result: Result<KFCrossPlatformImage, KingfisherError>, url: URL, response: URLResponse?
|
||||
)
|
||||
{
|
||||
if let image = try? result.get() {
|
||||
self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func startDownloadTask(
|
||||
context: DownloadingContext,
|
||||
callback: SessionDataTask.TaskCallback
|
||||
) -> DownloadTask
|
||||
{
|
||||
|
||||
let downloadTask = addDownloadTask(context: context, callback: callback)
|
||||
|
||||
let sessionTask = downloadTask.sessionTask
|
||||
guard !sessionTask.started else {
|
||||
return downloadTask
|
||||
}
|
||||
|
||||
sessionTask.onTaskDone.delegate(on: self) { (self, done) in
|
||||
// Underlying downloading finishes.
|
||||
// result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback]
|
||||
let (result, callbacks) = done
|
||||
|
||||
// Before processing the downloaded data.
|
||||
self.reportDidDownloadImageData(result: result, url: context.url)
|
||||
|
||||
switch result {
|
||||
// Download finished. Now process the data to an image.
|
||||
case .success(let (data, response)):
|
||||
let processor = ImageDataProcessor(
|
||||
data: data, callbacks: callbacks, processingQueue: context.options.processingQueue
|
||||
)
|
||||
processor.onImageProcessed.delegate(on: self) { (self, done) in
|
||||
// `onImageProcessed` will be called for `callbacks.count` times, with each
|
||||
// `SessionDataTask.TaskCallback` as the input parameter.
|
||||
// result: Result<Image>, callback: SessionDataTask.TaskCallback
|
||||
let (result, callback) = done
|
||||
|
||||
self.reportDidProcessImage(result: result, url: context.url, response: response)
|
||||
|
||||
let imageResult = result.map { ImageLoadingResult(image: $0, url: context.url, originalData: data) }
|
||||
let queue = callback.options.callbackQueue
|
||||
queue.execute { callback.onCompleted?.call(imageResult) }
|
||||
}
|
||||
processor.process()
|
||||
|
||||
case .failure(let error):
|
||||
callbacks.forEach { callback in
|
||||
let queue = callback.options.callbackQueue
|
||||
queue.execute { callback.onCompleted?.call(.failure(error)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reportWillDownloadImage(url: context.url, request: context.request)
|
||||
sessionTask.resume()
|
||||
return downloadTask
|
||||
}
|
||||
|
||||
// MARK: Downloading Task
|
||||
/// Downloads an image with a URL and option. Invoked internally by Kingfisher. Subclasses must invoke super.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - url: Target URL.
|
||||
/// - options: The options could control download behavior. See `KingfisherOptionsInfo`.
|
||||
/// - completionHandler: Called when the download progress finishes. This block will be called in the queue
|
||||
/// defined in `.callbackQueue` in `options` parameter.
|
||||
/// - Returns: A downloading task. You could call `cancel` on it to stop the download task.
|
||||
@discardableResult
|
||||
open func downloadImage(
|
||||
with url: URL,
|
||||
options: KingfisherParsedOptionsInfo,
|
||||
completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
|
||||
{
|
||||
var downloadTask: DownloadTask?
|
||||
createDownloadContext(with: url, options: options) { result in
|
||||
switch result {
|
||||
case .success(let context):
|
||||
// `downloadTask` will be set if the downloading started immediately. This is the case when no request
|
||||
// modifier or a sync modifier (`ImageDownloadRequestModifier`) is used. Otherwise, when an
|
||||
// `AsyncImageDownloadRequestModifier` is used the returned `downloadTask` of this method will be `nil`
|
||||
// and the actual "delayed" task is given in `AsyncImageDownloadRequestModifier.onDownloadTaskStarted`
|
||||
// callback.
|
||||
downloadTask = self.startDownloadTask(
|
||||
context: context,
|
||||
callback: self.createTaskCallback(completionHandler, options: options)
|
||||
)
|
||||
if let modifier = options.requestModifier {
|
||||
modifier.onDownloadTaskStarted?(downloadTask)
|
||||
}
|
||||
case .failure(let error):
|
||||
options.callbackQueue.execute {
|
||||
completionHandler?(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return downloadTask
|
||||
}
|
||||
|
||||
/// Downloads an image with a URL and option.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - url: Target URL.
|
||||
/// - options: The options could control download behavior. See `KingfisherOptionsInfo`.
|
||||
/// - progressBlock: Called when the download progress updated. This block will be always be called in main queue.
|
||||
/// - completionHandler: Called when the download progress finishes. This block will be called in the queue
|
||||
/// defined in `.callbackQueue` in `options` parameter.
|
||||
/// - Returns: A downloading task. You could call `cancel` on it to stop the download task.
|
||||
@discardableResult
|
||||
open func downloadImage(
|
||||
with url: URL,
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
progressBlock: DownloadProgressBlock? = nil,
|
||||
completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
|
||||
{
|
||||
var info = KingfisherParsedOptionsInfo(options)
|
||||
if let block = progressBlock {
|
||||
info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
|
||||
}
|
||||
return downloadImage(
|
||||
with: url,
|
||||
options: info,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/// Downloads an image with a URL and option.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - url: Target URL.
|
||||
/// - options: The options could control download behavior. See `KingfisherOptionsInfo`.
|
||||
/// - completionHandler: Called when the download progress finishes. This block will be called in the queue
|
||||
/// defined in `.callbackQueue` in `options` parameter.
|
||||
/// - Returns: A downloading task. You could call `cancel` on it to stop the download task.
|
||||
@discardableResult
|
||||
open func downloadImage(
|
||||
with url: URL,
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
|
||||
{
|
||||
downloadImage(
|
||||
with: url,
|
||||
options: KingfisherParsedOptionsInfo(options),
|
||||
completionHandler: completionHandler
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Cancelling Task
|
||||
extension ImageDownloader {
|
||||
|
||||
/// Cancel all downloading tasks for this `ImageDownloader`. It will trigger the completion handlers
|
||||
/// for all not-yet-finished downloading tasks.
|
||||
///
|
||||
/// If you need to only cancel a certain task, call `cancel()` on the `DownloadTask`
|
||||
/// returned by the downloading methods. If you need to cancel all `DownloadTask`s of a certain url,
|
||||
/// use `ImageDownloader.cancel(url:)`.
|
||||
public func cancelAll() {
|
||||
sessionDelegate.cancelAll()
|
||||
}
|
||||
|
||||
/// Cancel all downloading tasks for a given URL. It will trigger the completion handlers for
|
||||
/// all not-yet-finished downloading tasks for the URL.
|
||||
///
|
||||
/// - Parameter url: The URL which you want to cancel downloading.
|
||||
public func cancel(url: URL) {
|
||||
sessionDelegate.cancel(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
// Use the default implementation from extension of `AuthenticationChallengeResponsible`.
|
||||
extension ImageDownloader: AuthenticationChallengeResponsible {}
|
||||
|
||||
// Use the default implementation from extension of `ImageDownloaderDelegate`.
|
||||
extension ImageDownloader: ImageDownloaderDelegate {}
|
||||
|
||||
extension ImageDownloader {
|
||||
struct DownloadingContext {
|
||||
let url: URL
|
||||
let request: URLRequest
|
||||
let options: KingfisherParsedOptionsInfo
|
||||
}
|
||||
}
|
||||
184
Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift
generated
Normal file
184
Pods/Kingfisher/Sources/Networking/ImageDownloaderDelegate.swift
generated
Normal file
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// ImageDownloaderDelegate.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2018/10/11.
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Protocol of `ImageDownloader`. This protocol provides a set of methods which are related to image downloader
|
||||
/// working stages and rules.
|
||||
public protocol ImageDownloaderDelegate: AnyObject {
|
||||
|
||||
/// Called when the `ImageDownloader` object will start downloading an image from a specified URL.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The `ImageDownloader` object which is used for the downloading operation.
|
||||
/// - url: URL of the starting request.
|
||||
/// - request: The request object for the download process.
|
||||
///
|
||||
func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?)
|
||||
|
||||
/// Called when the `ImageDownloader` completes a downloading request with success or failure.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The `ImageDownloader` object which is used for the downloading operation.
|
||||
/// - url: URL of the original request URL.
|
||||
/// - response: The response object of the downloading process.
|
||||
/// - error: The error in case of failure.
|
||||
///
|
||||
func imageDownloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didFinishDownloadingImageForURL url: URL,
|
||||
with response: URLResponse?,
|
||||
error: Error?)
|
||||
|
||||
/// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is
|
||||
/// your last chance to verify or modify the downloaded data before Kingfisher tries to perform addition
|
||||
/// processing on the image data.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The `ImageDownloader` object which is used for the downloading operation.
|
||||
/// - data: The original downloaded data.
|
||||
/// - dataTask: The data task contains request and response information of the download.
|
||||
/// - Note:
|
||||
/// This can be used to pre-process raw image data before creation of `Image` instance (i.e.
|
||||
/// decrypting or verification). If `nil` returned, the processing is interrupted and a `KingfisherError` with
|
||||
/// `ResponseErrorReason.dataModifyingFailed` will be raised. You could use this fact to stop the image
|
||||
/// processing flow if you find the data is corrupted or malformed.
|
||||
///
|
||||
/// If this method is implemented, `imageDownloader(_:didDownload:for:)` will not be called anymore.
|
||||
func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, with dataTask: SessionDataTask) -> Data?
|
||||
|
||||
/// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is
|
||||
/// your last chance to verify or modify the downloaded data before Kingfisher tries to perform addition
|
||||
/// processing on the image data.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The `ImageDownloader` object which is used for the downloading operation.
|
||||
/// - data: The original downloaded data.
|
||||
/// - url: The URL of the original request URL.
|
||||
/// - Returns: The data from which Kingfisher should use to create an image. You need to provide valid data
|
||||
/// which content is one of the supported image file format. Kingfisher will perform process on this
|
||||
/// data and try to convert it to an image object.
|
||||
/// - Note:
|
||||
/// This can be used to pre-process raw image data before creation of `Image` instance (i.e.
|
||||
/// decrypting or verification). If `nil` returned, the processing is interrupted and a `KingfisherError` with
|
||||
/// `ResponseErrorReason.dataModifyingFailed` will be raised. You could use this fact to stop the image
|
||||
/// processing flow if you find the data is corrupted or malformed.
|
||||
///
|
||||
/// If `imageDownloader(_:didDownload:with:)` is implemented, this method will not be called anymore.
|
||||
func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data?
|
||||
|
||||
/// Called when the `ImageDownloader` object successfully downloads and processes an image from specified URL.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The `ImageDownloader` object which is used for the downloading operation.
|
||||
/// - image: The downloaded and processed image.
|
||||
/// - url: URL of the original request URL.
|
||||
/// - response: The original response object of the downloading process.
|
||||
///
|
||||
func imageDownloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didDownload image: KFCrossPlatformImage,
|
||||
for url: URL,
|
||||
with response: URLResponse?)
|
||||
|
||||
/// Checks if a received HTTP status code is valid or not.
|
||||
/// By default, a status code in range 200..<400 is considered as valid.
|
||||
/// If an invalid code is received, the downloader will raise an `KingfisherError` with
|
||||
/// `ResponseErrorReason.invalidHTTPStatusCode` as its reason.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - code: The received HTTP status code.
|
||||
/// - downloader: The `ImageDownloader` object asks for validate status code.
|
||||
/// - Returns: Returns a value to indicate whether this HTTP status code is valid or not.
|
||||
/// - Note: If the default 200 to 400 valid code does not suit your need,
|
||||
/// you can implement this method to change that behavior.
|
||||
func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool
|
||||
|
||||
/// Called when the task has received a valid HTTP response after it passes other checks such as the status code.
|
||||
/// You can perform additional checks or verification on the response to determine if the download should be allowed.
|
||||
///
|
||||
/// For example, it is useful if you want to verify some header values in the response before actually starting the
|
||||
/// download.
|
||||
///
|
||||
/// If implemented, it is your responsibility to call the `completionHandler` with a proper response disposition,
|
||||
/// such as `.allow` to start the actual downloading or `.cancel` to cancel the task. If `.cancel` is used as the
|
||||
/// disposition, the downloader will raise an `KingfisherError` with
|
||||
/// `ResponseErrorReason.cancelledByDelegate` as its reason. If not implemented, any response which passes other
|
||||
/// checked will be allowed and the download starts.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - downloader: The `ImageDownloader` object which is used for the downloading operation.
|
||||
/// - response: The original response object of the downloading process.
|
||||
/// - completionHandler: A completion handler that receives the disposition for the download task. You must call
|
||||
/// this handler with either `.allow` or `.cancel`.
|
||||
func imageDownloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didReceive response: URLResponse,
|
||||
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
|
||||
}
|
||||
|
||||
// Default implementation for `ImageDownloaderDelegate`.
|
||||
extension ImageDownloaderDelegate {
|
||||
public func imageDownloader(
|
||||
_ downloader: ImageDownloader,
|
||||
willDownloadImageForURL url: URL,
|
||||
with request: URLRequest?) {}
|
||||
|
||||
public func imageDownloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didFinishDownloadingImageForURL url: URL,
|
||||
with response: URLResponse?,
|
||||
error: Error?) {}
|
||||
|
||||
public func imageDownloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didDownload image: KFCrossPlatformImage,
|
||||
for url: URL,
|
||||
with response: URLResponse?) {}
|
||||
|
||||
public func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool {
|
||||
return (200..<400).contains(code)
|
||||
}
|
||||
|
||||
public func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, with task: SessionDataTask) -> Data? {
|
||||
guard let url = task.originalURL else {
|
||||
return data
|
||||
}
|
||||
return imageDownloader(downloader, didDownload: data, for: url)
|
||||
}
|
||||
|
||||
public func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? {
|
||||
return data
|
||||
}
|
||||
|
||||
public func imageDownloader(
|
||||
_ downloader: ImageDownloader,
|
||||
didReceive response: URLResponse,
|
||||
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||||
completionHandler(.allow)
|
||||
}
|
||||
|
||||
}
|
||||
116
Pods/Kingfisher/Sources/Networking/ImageModifier.swift
generated
Normal file
116
Pods/Kingfisher/Sources/Networking/ImageModifier.swift
generated
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// ImageModifier.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Ethan Gill on 2017/11/28.
|
||||
//
|
||||
// Copyright (c) 2019 Ethan Gill <ethan.gill@me.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
|
||||
|
||||
/// An `ImageModifier` can be used to change properties on an image between cache serialization and the actual use of
|
||||
/// the image. The `modify(_:)` method will be called after the image retrieved from its source and before it returned
|
||||
/// to the caller. This modified image is expected to be only used for rendering purpose, any changes applied by the
|
||||
/// `ImageModifier` will not be serialized or cached.
|
||||
public protocol ImageModifier {
|
||||
/// Modify an input `Image`.
|
||||
///
|
||||
/// - parameter image: Image which will be modified by `self`
|
||||
///
|
||||
/// - returns: The modified image.
|
||||
///
|
||||
/// - Note: The return value will be unmodified if modifying is not possible on
|
||||
/// the current platform.
|
||||
/// - Note: Most modifiers support UIImage or NSImage, but not CGImage.
|
||||
func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage
|
||||
}
|
||||
|
||||
/// A wrapper for creating an `ImageModifier` easier.
|
||||
/// This type conforms to `ImageModifier` and wraps an image modify block.
|
||||
/// If the `block` throws an error, the original image will be used.
|
||||
public struct AnyImageModifier: ImageModifier {
|
||||
|
||||
/// A block which modifies images, or returns the original image
|
||||
/// if modification cannot be performed with an error.
|
||||
let block: (KFCrossPlatformImage) throws -> KFCrossPlatformImage
|
||||
|
||||
/// Creates an `AnyImageModifier` with a given `modify` block.
|
||||
public init(modify: @escaping (KFCrossPlatformImage) throws -> KFCrossPlatformImage) {
|
||||
block = modify
|
||||
}
|
||||
|
||||
/// Modify an input `Image`. See `ImageModifier` protocol for more.
|
||||
public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
|
||||
return (try? block(image)) ?? image
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
|
||||
/// Modifier for setting the rendering mode of images.
|
||||
public struct RenderingModeImageModifier: ImageModifier {
|
||||
|
||||
/// The rendering mode to apply to the image.
|
||||
public let renderingMode: UIImage.RenderingMode
|
||||
|
||||
/// Creates a `RenderingModeImageModifier`.
|
||||
///
|
||||
/// - Parameter renderingMode: The rendering mode to apply to the image. Default is `.automatic`.
|
||||
public init(renderingMode: UIImage.RenderingMode = .automatic) {
|
||||
self.renderingMode = renderingMode
|
||||
}
|
||||
|
||||
/// Modify an input `Image`. See `ImageModifier` protocol for more.
|
||||
public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
|
||||
return image.withRenderingMode(renderingMode)
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifier for setting the `flipsForRightToLeftLayoutDirection` property of images.
|
||||
public struct FlipsForRightToLeftLayoutDirectionImageModifier: ImageModifier {
|
||||
|
||||
/// Creates a `FlipsForRightToLeftLayoutDirectionImageModifier`.
|
||||
public init() {}
|
||||
|
||||
/// Modify an input `Image`. See `ImageModifier` protocol for more.
|
||||
public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
|
||||
return image.imageFlippedForRightToLeftLayoutDirection()
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifier for setting the `alignmentRectInsets` property of images.
|
||||
public struct AlignmentRectInsetsImageModifier: ImageModifier {
|
||||
|
||||
/// The alignment insets to apply to the image
|
||||
public let alignmentInsets: UIEdgeInsets
|
||||
|
||||
/// Creates an `AlignmentRectInsetsImageModifier`.
|
||||
public init(alignmentInsets: UIEdgeInsets) {
|
||||
self.alignmentInsets = alignmentInsets
|
||||
}
|
||||
|
||||
/// Modify an input `Image`. See `ImageModifier` protocol for more.
|
||||
public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
|
||||
return image.withAlignmentRectInsets(alignmentInsets)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
442
Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift
generated
Normal file
442
Pods/Kingfisher/Sources/Networking/ImagePrefetcher.swift
generated
Normal file
@@ -0,0 +1,442 @@
|
||||
//
|
||||
// ImagePrefetcher.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Claire Knight <claire.knight@moggytech.co.uk> on 24/02/2016
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Progress update block of prefetcher when initialized with a list of resources.
|
||||
///
|
||||
/// - `skippedResources`: An array of resources that are already cached before the prefetching starting.
|
||||
/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while
|
||||
/// downloading, encountered an error when downloading or the download not being started at all.
|
||||
/// - `completedResources`: An array of resources that are downloaded and cached successfully.
|
||||
public typealias PrefetcherProgressBlock =
|
||||
((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void)
|
||||
|
||||
/// Progress update block of prefetcher when initialized with a list of resources.
|
||||
///
|
||||
/// - `skippedSources`: An array of sources that are already cached before the prefetching starting.
|
||||
/// - `failedSources`: An array of sources that fail to be fetched.
|
||||
/// - `completedResources`: An array of sources that are fetched and cached successfully.
|
||||
public typealias PrefetcherSourceProgressBlock =
|
||||
((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void)
|
||||
|
||||
/// Completion block of prefetcher when initialized with a list of sources.
|
||||
///
|
||||
/// - `skippedResources`: An array of resources that are already cached before the prefetching starting.
|
||||
/// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while
|
||||
/// downloading, encountered an error when downloading or the download not being started at all.
|
||||
/// - `completedResources`: An array of resources that are downloaded and cached successfully.
|
||||
public typealias PrefetcherCompletionHandler =
|
||||
((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void)
|
||||
|
||||
/// Completion block of prefetcher when initialized with a list of sources.
|
||||
///
|
||||
/// - `skippedSources`: An array of sources that are already cached before the prefetching starting.
|
||||
/// - `failedSources`: An array of sources that fail to be fetched.
|
||||
/// - `completedSources`: An array of sources that are fetched and cached successfully.
|
||||
public typealias PrefetcherSourceCompletionHandler =
|
||||
((_ skippedSources: [Source], _ failedSources: [Source], _ completedSources: [Source]) -> Void)
|
||||
|
||||
/// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them.
|
||||
/// This is useful when you know a list of image resources and want to download them before showing. It also works with
|
||||
/// some Cocoa prefetching mechanism like table view or collection view `prefetchDataSource`, to start image downloading
|
||||
/// and caching before they display on screen.
|
||||
public class ImagePrefetcher: CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
return "\(Unmanaged.passUnretained(self).toOpaque())"
|
||||
}
|
||||
|
||||
/// The maximum concurrent downloads to use when prefetching images. Default is 5.
|
||||
public var maxConcurrentDownloads = 5
|
||||
|
||||
private let prefetchSources: [Source]
|
||||
private let optionsInfo: KingfisherParsedOptionsInfo
|
||||
|
||||
private var progressBlock: PrefetcherProgressBlock?
|
||||
private var completionHandler: PrefetcherCompletionHandler?
|
||||
|
||||
private var progressSourceBlock: PrefetcherSourceProgressBlock?
|
||||
private var completionSourceHandler: PrefetcherSourceCompletionHandler?
|
||||
|
||||
private var tasks = [String: DownloadTask.WrappedTask]()
|
||||
|
||||
private var pendingSources: ArraySlice<Source>
|
||||
private var skippedSources = [Source]()
|
||||
private var completedSources = [Source]()
|
||||
private var failedSources = [Source]()
|
||||
|
||||
private var stopped = false
|
||||
|
||||
// A manager used for prefetching. We will use the helper methods in manager.
|
||||
private let manager: KingfisherManager
|
||||
|
||||
private let pretchQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.pretchQueue")
|
||||
private static let requestingQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImagePrefetcher.requestingQueue")
|
||||
|
||||
private var finished: Bool {
|
||||
let totalFinished: Int = failedSources.count + skippedSources.count + completedSources.count
|
||||
return totalFinished == prefetchSources.count && tasks.isEmpty
|
||||
}
|
||||
|
||||
/// Creates an image prefetcher with an array of URLs.
|
||||
///
|
||||
/// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable.
|
||||
/// After you get a valid `ImagePrefetcher` object, you call `start()` on it to begin the prefetching process.
|
||||
/// The images which are already cached will be skipped without downloading again.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - urls: The URLs which should be prefetched.
|
||||
/// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more.
|
||||
/// - progressBlock: Called every time an resource is downloaded, skipped or cancelled.
|
||||
/// - completionHandler: Called when the whole prefetching process finished.
|
||||
///
|
||||
/// - Note:
|
||||
/// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
|
||||
/// the downloader and cache target respectively. You can specify another downloader or cache by using
|
||||
/// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
|
||||
/// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
|
||||
public convenience init(
|
||||
urls: [URL],
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
progressBlock: PrefetcherProgressBlock? = nil,
|
||||
completionHandler: PrefetcherCompletionHandler? = nil)
|
||||
{
|
||||
let resources: [Resource] = urls.map { $0 }
|
||||
self.init(
|
||||
resources: resources,
|
||||
options: options,
|
||||
progressBlock: progressBlock,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/// Creates an image prefetcher with an array of URLs.
|
||||
///
|
||||
/// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable.
|
||||
/// After you get a valid `ImagePrefetcher` object, you call `start()` on it to begin the prefetching process.
|
||||
/// The images which are already cached will be skipped without downloading again.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - urls: The URLs which should be prefetched.
|
||||
/// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more.
|
||||
/// - completionHandler: Called when the whole prefetching process finished.
|
||||
///
|
||||
/// - Note:
|
||||
/// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
|
||||
/// the downloader and cache target respectively. You can specify another downloader or cache by using
|
||||
/// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
|
||||
/// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
|
||||
public convenience init(
|
||||
urls: [URL],
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
completionHandler: PrefetcherCompletionHandler? = nil)
|
||||
{
|
||||
let resources: [Resource] = urls.map { $0 }
|
||||
self.init(
|
||||
resources: resources,
|
||||
options: options,
|
||||
progressBlock: nil,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
/// Creates an image prefetcher with an array of resources.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - resources: The resources which should be prefetched. See `Resource` type for more.
|
||||
/// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more.
|
||||
/// - progressBlock: Called every time an resource is downloaded, skipped or cancelled.
|
||||
/// - completionHandler: Called when the whole prefetching process finished.
|
||||
///
|
||||
/// - Note:
|
||||
/// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
|
||||
/// the downloader and cache target respectively. You can specify another downloader or cache by using
|
||||
/// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
|
||||
/// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
|
||||
public convenience init(
|
||||
resources: [Resource],
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
progressBlock: PrefetcherProgressBlock? = nil,
|
||||
completionHandler: PrefetcherCompletionHandler? = nil)
|
||||
{
|
||||
self.init(sources: resources.map { $0.convertToSource() }, options: options)
|
||||
self.progressBlock = progressBlock
|
||||
self.completionHandler = completionHandler
|
||||
}
|
||||
|
||||
/// Creates an image prefetcher with an array of resources.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - resources: The resources which should be prefetched. See `Resource` type for more.
|
||||
/// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more.
|
||||
/// - completionHandler: Called when the whole prefetching process finished.
|
||||
///
|
||||
/// - Note:
|
||||
/// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
|
||||
/// the downloader and cache target respectively. You can specify another downloader or cache by using
|
||||
/// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
|
||||
/// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
|
||||
public convenience init(
|
||||
resources: [Resource],
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
completionHandler: PrefetcherCompletionHandler? = nil)
|
||||
{
|
||||
self.init(sources: resources.map { $0.convertToSource() }, options: options)
|
||||
self.completionHandler = completionHandler
|
||||
}
|
||||
|
||||
/// Creates an image prefetcher with an array of sources.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - sources: The sources which should be prefetched. See `Source` type for more.
|
||||
/// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more.
|
||||
/// - progressBlock: Called every time an source fetching successes, fails, is skipped.
|
||||
/// - completionHandler: Called when the whole prefetching process finished.
|
||||
///
|
||||
/// - Note:
|
||||
/// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
|
||||
/// the downloader and cache target respectively. You can specify another downloader or cache by using
|
||||
/// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
|
||||
/// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
|
||||
public convenience init(sources: [Source],
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
progressBlock: PrefetcherSourceProgressBlock? = nil,
|
||||
completionHandler: PrefetcherSourceCompletionHandler? = nil)
|
||||
{
|
||||
self.init(sources: sources, options: options)
|
||||
self.progressSourceBlock = progressBlock
|
||||
self.completionSourceHandler = completionHandler
|
||||
}
|
||||
|
||||
/// Creates an image prefetcher with an array of sources.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - sources: The sources which should be prefetched. See `Source` type for more.
|
||||
/// - options: Options could control some behaviors. See `KingfisherOptionsInfo` for more.
|
||||
/// - completionHandler: Called when the whole prefetching process finished.
|
||||
///
|
||||
/// - Note:
|
||||
/// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
|
||||
/// the downloader and cache target respectively. You can specify another downloader or cache by using
|
||||
/// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
|
||||
/// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
|
||||
public convenience init(sources: [Source],
|
||||
options: KingfisherOptionsInfo? = nil,
|
||||
completionHandler: PrefetcherSourceCompletionHandler? = nil)
|
||||
{
|
||||
self.init(sources: sources, options: options)
|
||||
self.completionSourceHandler = completionHandler
|
||||
}
|
||||
|
||||
init(sources: [Source], options: KingfisherOptionsInfo?) {
|
||||
var options = KingfisherParsedOptionsInfo(options)
|
||||
prefetchSources = sources
|
||||
pendingSources = ArraySlice(sources)
|
||||
|
||||
// We want all callbacks from our prefetch queue, so we should ignore the callback queue in options.
|
||||
// Add our own callback dispatch queue to make sure all internal callbacks are
|
||||
// coming back in our expected queue.
|
||||
options.callbackQueue = .dispatch(pretchQueue)
|
||||
optionsInfo = options
|
||||
|
||||
let cache = optionsInfo.targetCache ?? .default
|
||||
let downloader = optionsInfo.downloader ?? .default
|
||||
manager = KingfisherManager(downloader: downloader, cache: cache)
|
||||
}
|
||||
|
||||
/// Starts to download the resources and cache them. This can be useful for background downloading
|
||||
/// of assets that are required for later use in an app. This code will not try and update any UI
|
||||
/// with the results of the process.
|
||||
public func start() {
|
||||
pretchQueue.async {
|
||||
guard !self.stopped else {
|
||||
assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.")
|
||||
self.handleComplete()
|
||||
return
|
||||
}
|
||||
|
||||
guard self.maxConcurrentDownloads > 0 else {
|
||||
assertionFailure("There should be concurrent downloads value should be at least 1.")
|
||||
self.handleComplete()
|
||||
return
|
||||
}
|
||||
|
||||
// Empty case.
|
||||
guard self.prefetchSources.count > 0 else {
|
||||
self.handleComplete()
|
||||
return
|
||||
}
|
||||
|
||||
let initialConcurrentDownloads = min(self.prefetchSources.count, self.maxConcurrentDownloads)
|
||||
for _ in 0 ..< initialConcurrentDownloads {
|
||||
if let resource = self.pendingSources.popFirst() {
|
||||
self.startPrefetching(resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops current downloading progress, and cancel any future prefetching activity that might be occuring.
|
||||
public func stop() {
|
||||
pretchQueue.async {
|
||||
if self.finished { return }
|
||||
self.stopped = true
|
||||
self.tasks.values.forEach { $0.cancel() }
|
||||
}
|
||||
}
|
||||
|
||||
private func downloadAndCache(_ source: Source) {
|
||||
|
||||
let downloadTaskCompletionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void) = { result in
|
||||
self.tasks.removeValue(forKey: source.cacheKey)
|
||||
do {
|
||||
let _ = try result.get()
|
||||
self.completedSources.append(source)
|
||||
} catch {
|
||||
self.failedSources.append(source)
|
||||
}
|
||||
|
||||
self.reportProgress()
|
||||
if self.stopped {
|
||||
if self.tasks.isEmpty {
|
||||
self.failedSources.append(contentsOf: self.pendingSources)
|
||||
self.handleComplete()
|
||||
}
|
||||
} else {
|
||||
self.reportCompletionOrStartNext()
|
||||
}
|
||||
}
|
||||
|
||||
var downloadTask: DownloadTask.WrappedTask?
|
||||
ImagePrefetcher.requestingQueue.sync {
|
||||
let context = RetrievingContext(
|
||||
options: optionsInfo, originalSource: source
|
||||
)
|
||||
downloadTask = manager.loadAndCacheImage(
|
||||
source: source,
|
||||
context: context,
|
||||
completionHandler: downloadTaskCompletionHandler)
|
||||
}
|
||||
|
||||
if let downloadTask = downloadTask {
|
||||
tasks[source.cacheKey] = downloadTask
|
||||
}
|
||||
}
|
||||
|
||||
private func append(cached source: Source) {
|
||||
skippedSources.append(source)
|
||||
|
||||
reportProgress()
|
||||
reportCompletionOrStartNext()
|
||||
}
|
||||
|
||||
private func startPrefetching(_ source: Source)
|
||||
{
|
||||
if optionsInfo.forceRefresh {
|
||||
downloadAndCache(source)
|
||||
return
|
||||
}
|
||||
|
||||
let cacheType = manager.cache.imageCachedType(
|
||||
forKey: source.cacheKey,
|
||||
processorIdentifier: optionsInfo.processor.identifier)
|
||||
switch cacheType {
|
||||
case .memory:
|
||||
append(cached: source)
|
||||
case .disk:
|
||||
if optionsInfo.alsoPrefetchToMemory {
|
||||
let context = RetrievingContext(options: optionsInfo, originalSource: source)
|
||||
_ = manager.retrieveImageFromCache(
|
||||
source: source,
|
||||
context: context)
|
||||
{
|
||||
_ in
|
||||
self.append(cached: source)
|
||||
}
|
||||
} else {
|
||||
append(cached: source)
|
||||
}
|
||||
case .none:
|
||||
downloadAndCache(source)
|
||||
}
|
||||
}
|
||||
|
||||
private func reportProgress() {
|
||||
|
||||
if progressBlock == nil && progressSourceBlock == nil {
|
||||
return
|
||||
}
|
||||
|
||||
let skipped = self.skippedSources
|
||||
let failed = self.failedSources
|
||||
let completed = self.completedSources
|
||||
CallbackQueue.mainCurrentOrAsync.execute {
|
||||
self.progressSourceBlock?(skipped, failed, completed)
|
||||
self.progressBlock?(
|
||||
skipped.compactMap { $0.asResource },
|
||||
failed.compactMap { $0.asResource },
|
||||
completed.compactMap { $0.asResource }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func reportCompletionOrStartNext() {
|
||||
if let resource = self.pendingSources.popFirst() {
|
||||
// Loose call stack for huge ammount of sources.
|
||||
pretchQueue.async { self.startPrefetching(resource) }
|
||||
} else {
|
||||
guard allFinished else { return }
|
||||
self.handleComplete()
|
||||
}
|
||||
}
|
||||
|
||||
var allFinished: Bool {
|
||||
return skippedSources.count + failedSources.count + completedSources.count == prefetchSources.count
|
||||
}
|
||||
|
||||
private func handleComplete() {
|
||||
|
||||
if completionHandler == nil && completionSourceHandler == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// The completion handler should be called on the main thread
|
||||
CallbackQueue.mainCurrentOrAsync.execute {
|
||||
self.completionSourceHandler?(self.skippedSources, self.failedSources, self.completedSources)
|
||||
self.completionHandler?(
|
||||
self.skippedSources.compactMap { $0.asResource },
|
||||
self.failedSources.compactMap { $0.asResource },
|
||||
self.completedSources.compactMap { $0.asResource }
|
||||
)
|
||||
self.completionHandler = nil
|
||||
self.progressBlock = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Pods/Kingfisher/Sources/Networking/RedirectHandler.swift
generated
Normal file
76
Pods/Kingfisher/Sources/Networking/RedirectHandler.swift
generated
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// RedirectHandler.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Roman Maidanovych on 2018/12/10.
|
||||
//
|
||||
// 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 and wraps a method for modifying request during an image download request redirection.
|
||||
public protocol ImageDownloadRedirectHandler {
|
||||
|
||||
/// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection.
|
||||
/// This is the posibility you can modify the image download request during redirection. 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.
|
||||
///
|
||||
/// Usually, you pass an `ImageDownloadRedirectHandler` as the associated value of
|
||||
/// `KingfisherOptionsInfoItem.redirectHandler` and use it as the `options` parameter in related methods.
|
||||
///
|
||||
/// If you do nothing with the input `request` and return it as is, a downloading process will redirect with it.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - task: The current `SessionDataTask` which triggers this redirect.
|
||||
/// - response: The response received during redirection.
|
||||
/// - newRequest: The request for redirection which can be modified.
|
||||
/// - completionHandler: A closure for being called with modified request.
|
||||
func handleHTTPRedirection(
|
||||
for task: SessionDataTask,
|
||||
response: HTTPURLResponse,
|
||||
newRequest: URLRequest,
|
||||
completionHandler: @escaping (URLRequest?) -> Void)
|
||||
}
|
||||
|
||||
/// A wrapper for creating an `ImageDownloadRedirectHandler` easier.
|
||||
/// This type conforms to `ImageDownloadRedirectHandler` and wraps a redirect request modify block.
|
||||
public struct AnyRedirectHandler: ImageDownloadRedirectHandler {
|
||||
|
||||
let block: (SessionDataTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void
|
||||
|
||||
public func handleHTTPRedirection(
|
||||
for task: SessionDataTask,
|
||||
response: HTTPURLResponse,
|
||||
newRequest: URLRequest,
|
||||
completionHandler: @escaping (URLRequest?) -> Void)
|
||||
{
|
||||
block(task, response, newRequest, completionHandler)
|
||||
}
|
||||
|
||||
/// Creates a value of `ImageDownloadRedirectHandler` which runs `modify` block.
|
||||
///
|
||||
/// - Parameter modify: The request modifying block runs when a request modifying task comes.
|
||||
///
|
||||
public init(handle: @escaping (SessionDataTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void) {
|
||||
block = handle
|
||||
}
|
||||
}
|
||||
108
Pods/Kingfisher/Sources/Networking/RequestModifier.swift
generated
Normal file
108
Pods/Kingfisher/Sources/Networking/RequestModifier.swift
generated
Normal file
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// RequestModifier.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2016/09/05.
|
||||
//
|
||||
// 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 and wraps a method for modifying request before an image download request starts in an asynchronous way.
|
||||
public protocol AsyncImageDownloadRequestModifier {
|
||||
|
||||
/// This method will be called just before the `request` 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.
|
||||
/// When you have done with the modification, call the `reportModified` block with the modified request and the data
|
||||
/// download will happen with this request.
|
||||
///
|
||||
/// Usually, you pass an `AsyncImageDownloadRequestModifier` as the associated value of
|
||||
/// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods.
|
||||
///
|
||||
/// If you do nothing with the input `request` and return it as is, a downloading process will start with it.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: The input request contains necessary information like `url`. This request is generated
|
||||
/// according to your resource url as a GET request.
|
||||
/// - reportModified: The callback block you need to call after the asynchronous modifying done.
|
||||
///
|
||||
func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void)
|
||||
|
||||
/// A block will be called when the download task started.
|
||||
///
|
||||
/// If an `AsyncImageDownloadRequestModifier` and the asynchronous modification happens before the download, the
|
||||
/// related download method will not return a valid `DownloadTask` value. Instead, you can get one from this method.
|
||||
var onDownloadTaskStarted: ((DownloadTask?) -> Void)? { get }
|
||||
}
|
||||
|
||||
/// Represents and wraps a method for modifying request before an image download request starts.
|
||||
public protocol ImageDownloadRequestModifier: AsyncImageDownloadRequestModifier {
|
||||
|
||||
/// This method will be called just before the `request` 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.
|
||||
///
|
||||
/// Usually, you pass an `ImageDownloadRequestModifier` as the associated value of
|
||||
/// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods.
|
||||
///
|
||||
/// If you do nothing with the input `request` and return it as is, a downloading process will start with it.
|
||||
///
|
||||
/// - Parameter request: The input request contains necessary information like `url`. This request is generated
|
||||
/// according to your resource url as a GET request.
|
||||
/// - Returns: A modified version of request, which you wish to use for downloading an image. If `nil` returned,
|
||||
/// a `KingfisherError.requestError` with `.emptyRequest` as its reason will occur.
|
||||
///
|
||||
func modified(for request: URLRequest) -> URLRequest?
|
||||
}
|
||||
|
||||
extension ImageDownloadRequestModifier {
|
||||
public func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) {
|
||||
let request = modified(for: request)
|
||||
reportModified(request)
|
||||
}
|
||||
|
||||
/// This is `nil` for a sync `ImageDownloadRequestModifier` by default. You can get the `DownloadTask` from the
|
||||
/// return value of downloader method.
|
||||
public var onDownloadTaskStarted: ((DownloadTask?) -> Void)? { return nil }
|
||||
}
|
||||
|
||||
/// A wrapper for creating an `ImageDownloadRequestModifier` easier.
|
||||
/// This type conforms to `ImageDownloadRequestModifier` and wraps an image modify block.
|
||||
public struct AnyModifier: ImageDownloadRequestModifier {
|
||||
|
||||
let block: (URLRequest) -> URLRequest?
|
||||
|
||||
/// For `ImageDownloadRequestModifier` conformation.
|
||||
public func modified(for request: URLRequest) -> URLRequest? {
|
||||
return block(request)
|
||||
}
|
||||
|
||||
/// Creates a value of `ImageDownloadRequestModifier` which runs `modify` block.
|
||||
///
|
||||
/// - Parameter modify: The request modifying block runs when a request modifying task comes.
|
||||
/// The return `URLRequest?` value of this block will be used as the image download request.
|
||||
/// If `nil` returned, a `KingfisherError.requestError` with `.emptyRequest` as its
|
||||
/// reason will occur.
|
||||
public init(modify: @escaping (URLRequest) -> URLRequest?) {
|
||||
block = modify
|
||||
}
|
||||
}
|
||||
153
Pods/Kingfisher/Sources/Networking/RetryStrategy.swift
generated
Normal file
153
Pods/Kingfisher/Sources/Networking/RetryStrategy.swift
generated
Normal file
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// RetryStrategy.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2020/05/04.
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Represents a retry context which could be used to determine the current retry status.
|
||||
public class RetryContext {
|
||||
|
||||
/// The source from which the target image should be retrieved.
|
||||
public let source: Source
|
||||
|
||||
/// The last error which caused current retry behavior.
|
||||
public let error: KingfisherError
|
||||
|
||||
/// The retried count before current retry happens. This value is `0` if the current retry is for the first time.
|
||||
public var retriedCount: Int
|
||||
|
||||
/// A user set value for passing any other information during the retry. If you choose to use `RetryDecision.retry`
|
||||
/// as the retry decision for `RetryStrategy.retry(context:retryHandler:)`, the associated value of
|
||||
/// `RetryDecision.retry` will be delivered to you in the next retry.
|
||||
public internal(set) var userInfo: Any? = nil
|
||||
|
||||
init(source: Source, error: KingfisherError) {
|
||||
self.source = source
|
||||
self.error = error
|
||||
self.retriedCount = 0
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func increaseRetryCount() -> RetryContext {
|
||||
retriedCount += 1
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents decision of behavior on the current retry.
|
||||
public enum RetryDecision {
|
||||
/// A retry should happen. The associated `userInfo` will be pass to the next retry in the `RetryContext` parameter.
|
||||
case retry(userInfo: Any?)
|
||||
/// There should be no more retry attempt. The image retrieving process will fail with an error.
|
||||
case stop
|
||||
}
|
||||
|
||||
/// Defines a retry strategy can be applied to a `.retryStrategy` option.
|
||||
public protocol RetryStrategy {
|
||||
|
||||
/// Kingfisher calls this method if an error happens during the image retrieving process from a `KingfisherManager`.
|
||||
/// You implement this method to provide necessary logic based on the `context` parameter. Then you need to call
|
||||
/// `retryHandler` to pass the retry decision back to Kingfisher.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - context: The retry context containing information of current retry attempt.
|
||||
/// - retryHandler: A block you need to call with a decision of whether the retry should happen or not.
|
||||
func retry(context: RetryContext, retryHandler: @escaping (RetryDecision) -> Void)
|
||||
}
|
||||
|
||||
/// A retry strategy that guides Kingfisher to retry when a `.responseError` happens, with a specified max retry count
|
||||
/// and a certain interval mechanism.
|
||||
public struct DelayRetryStrategy: RetryStrategy {
|
||||
|
||||
/// Represents the interval mechanism which used in a `DelayRetryStrategy`.
|
||||
public enum Interval {
|
||||
/// The next retry attempt should happen in fixed seconds. For example, if the associated value is 3, the
|
||||
/// attempts happens after 3 seconds after the previous decision is made.
|
||||
case seconds(TimeInterval)
|
||||
/// The next retry attempt should happen in an accumulated duration. For example, if the associated value is 3,
|
||||
/// the attempts happens with interval of 3, 6, 9, 12, ... seconds.
|
||||
case accumulated(TimeInterval)
|
||||
/// Uses a block to determine the next interval. The current retry count is given as a parameter.
|
||||
case custom(block: (_ retriedCount: Int) -> TimeInterval)
|
||||
|
||||
func timeInterval(for retriedCount: Int) -> TimeInterval {
|
||||
let retryAfter: TimeInterval
|
||||
switch self {
|
||||
case .seconds(let interval):
|
||||
retryAfter = interval
|
||||
case .accumulated(let interval):
|
||||
retryAfter = Double(retriedCount + 1) * interval
|
||||
case .custom(let block):
|
||||
retryAfter = block(retriedCount)
|
||||
}
|
||||
return retryAfter
|
||||
}
|
||||
}
|
||||
|
||||
/// The max retry count defined for the retry strategy
|
||||
public let maxRetryCount: Int
|
||||
|
||||
/// The retry interval mechanism defined for the retry strategy.
|
||||
public let retryInterval: Interval
|
||||
|
||||
/// Creates a delay retry strategy.
|
||||
/// - Parameters:
|
||||
/// - maxRetryCount: The max retry count.
|
||||
/// - retryInterval: The retry interval mechanism. By default, `.seconds(3)` is used to provide a constant retry
|
||||
/// interval.
|
||||
public init(maxRetryCount: Int, retryInterval: Interval = .seconds(3)) {
|
||||
self.maxRetryCount = maxRetryCount
|
||||
self.retryInterval = retryInterval
|
||||
}
|
||||
|
||||
public func retry(context: RetryContext, retryHandler: @escaping (RetryDecision) -> Void) {
|
||||
// Retry count exceeded.
|
||||
guard context.retriedCount < maxRetryCount else {
|
||||
retryHandler(.stop)
|
||||
return
|
||||
}
|
||||
|
||||
// User cancel the task. No retry.
|
||||
guard !context.error.isTaskCancelled else {
|
||||
retryHandler(.stop)
|
||||
return
|
||||
}
|
||||
|
||||
// Only retry for a response error.
|
||||
guard case KingfisherError.responseError = context.error else {
|
||||
retryHandler(.stop)
|
||||
return
|
||||
}
|
||||
|
||||
let interval = retryInterval.timeInterval(for: context.retriedCount)
|
||||
if interval == 0 {
|
||||
retryHandler(.retry(userInfo: nil))
|
||||
} else {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
|
||||
retryHandler(.retry(userInfo: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Pods/Kingfisher/Sources/Networking/SessionDataTask.swift
generated
Normal file
127
Pods/Kingfisher/Sources/Networking/SessionDataTask.swift
generated
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// SessionDataTask.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2018/11/1.
|
||||
//
|
||||
// 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 session data task in `ImageDownloader`. It consists of an underlying `URLSessionDataTask` and
|
||||
/// an array of `TaskCallback`. Multiple `TaskCallback`s could be added for a single downloading data task.
|
||||
public class SessionDataTask {
|
||||
|
||||
/// Represents the type of token which used for cancelling a task.
|
||||
public typealias CancelToken = Int
|
||||
|
||||
struct TaskCallback {
|
||||
let onCompleted: Delegate<Result<ImageLoadingResult, KingfisherError>, Void>?
|
||||
let options: KingfisherParsedOptionsInfo
|
||||
}
|
||||
|
||||
/// Downloaded raw data of current task.
|
||||
public private(set) var mutableData: Data
|
||||
|
||||
// This is a copy of `task.originalRequest?.url`. It is for getting a race-safe behavior for a pitfall on iOS 13.
|
||||
// Ref: https://github.com/onevcat/Kingfisher/issues/1511
|
||||
public let originalURL: URL?
|
||||
|
||||
/// The underlying download task. It is only for debugging purpose when you encountered an error. You should not
|
||||
/// modify the content of this task or start it yourself.
|
||||
public let task: URLSessionDataTask
|
||||
private var callbacksStore = [CancelToken: TaskCallback]()
|
||||
|
||||
var callbacks: [SessionDataTask.TaskCallback] {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return Array(callbacksStore.values)
|
||||
}
|
||||
|
||||
private var currentToken = 0
|
||||
private let lock = NSLock()
|
||||
|
||||
let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>()
|
||||
let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>()
|
||||
|
||||
var started = false
|
||||
var containsCallbacks: Bool {
|
||||
// We should be able to use `task.state != .running` to check it.
|
||||
// However, in some rare cases, cancelling the task does not change
|
||||
// task state to `.cancelling` immediately, but still in `.running`.
|
||||
// So we need to check callbacks count to for sure that it is safe to remove the
|
||||
// task in delegate.
|
||||
return !callbacks.isEmpty
|
||||
}
|
||||
|
||||
init(task: URLSessionDataTask) {
|
||||
self.task = task
|
||||
self.originalURL = task.originalRequest?.url
|
||||
mutableData = Data()
|
||||
}
|
||||
|
||||
func addCallback(_ callback: TaskCallback) -> CancelToken {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
callbacksStore[currentToken] = callback
|
||||
defer { currentToken += 1 }
|
||||
return currentToken
|
||||
}
|
||||
|
||||
func removeCallback(_ token: CancelToken) -> TaskCallback? {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
if let callback = callbacksStore[token] {
|
||||
callbacksStore[token] = nil
|
||||
return callback
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAllCallbacks() -> Void {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
callbacksStore.removeAll()
|
||||
}
|
||||
|
||||
func resume() {
|
||||
guard !started else { return }
|
||||
started = true
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func cancel(token: CancelToken) {
|
||||
guard let callback = removeCallback(token) else {
|
||||
return
|
||||
}
|
||||
onCallbackCancelled.call((token, callback))
|
||||
}
|
||||
|
||||
func forceCancel() {
|
||||
for token in callbacksStore.keys {
|
||||
cancel(token: token)
|
||||
}
|
||||
}
|
||||
|
||||
func didReceiveData(_ data: Data) {
|
||||
mutableData.append(data)
|
||||
}
|
||||
}
|
||||
271
Pods/Kingfisher/Sources/Networking/SessionDelegate.swift
generated
Normal file
271
Pods/Kingfisher/Sources/Networking/SessionDelegate.swift
generated
Normal file
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// SessionDelegate.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2018/11/1.
|
||||
//
|
||||
// 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 the delegate object of downloader session. It also behave like a task manager for downloading.
|
||||
@objc(KFSessionDelegate) // Fix for ObjC header name conflicting. https://github.com/onevcat/Kingfisher/issues/1530
|
||||
open class SessionDelegate: NSObject {
|
||||
|
||||
typealias SessionChallengeFunc = (
|
||||
URLSession,
|
||||
URLAuthenticationChallenge,
|
||||
(URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
||||
)
|
||||
|
||||
typealias SessionTaskChallengeFunc = (
|
||||
URLSession,
|
||||
URLSessionTask,
|
||||
URLAuthenticationChallenge,
|
||||
(URLSession.AuthChallengeDisposition, URLCredential?) -> Void
|
||||
)
|
||||
|
||||
private var tasks: [URL: SessionDataTask] = [:]
|
||||
private let lock = NSLock()
|
||||
|
||||
let onValidStatusCode = Delegate<Int, Bool>()
|
||||
let onResponseReceived = Delegate<(URLResponse, (URLSession.ResponseDisposition) -> Void), Void>()
|
||||
let onDownloadingFinished = Delegate<(URL, Result<URLResponse, KingfisherError>), Void>()
|
||||
let onDidDownloadData = Delegate<SessionDataTask, Data?>()
|
||||
|
||||
let onReceiveSessionChallenge = Delegate<SessionChallengeFunc, Void>()
|
||||
let onReceiveSessionTaskChallenge = Delegate<SessionTaskChallengeFunc, Void>()
|
||||
|
||||
func add(
|
||||
_ dataTask: URLSessionDataTask,
|
||||
url: URL,
|
||||
callback: SessionDataTask.TaskCallback) -> DownloadTask
|
||||
{
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
// Create a new task if necessary.
|
||||
let task = SessionDataTask(task: dataTask)
|
||||
task.onCallbackCancelled.delegate(on: self) { [weak task] (self, value) in
|
||||
guard let task = task else { return }
|
||||
|
||||
let (token, callback) = value
|
||||
|
||||
let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token))
|
||||
task.onTaskDone.call((.failure(error), [callback]))
|
||||
// No other callbacks waiting, we can clear the task now.
|
||||
if !task.containsCallbacks {
|
||||
let dataTask = task.task
|
||||
|
||||
self.cancelTask(dataTask)
|
||||
self.remove(task)
|
||||
}
|
||||
}
|
||||
let token = task.addCallback(callback)
|
||||
tasks[url] = task
|
||||
return DownloadTask(sessionTask: task, cancelToken: token)
|
||||
}
|
||||
|
||||
private func cancelTask(_ dataTask: URLSessionDataTask) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
dataTask.cancel()
|
||||
}
|
||||
|
||||
func append(
|
||||
_ task: SessionDataTask,
|
||||
callback: SessionDataTask.TaskCallback) -> DownloadTask
|
||||
{
|
||||
let token = task.addCallback(callback)
|
||||
return DownloadTask(sessionTask: task, cancelToken: token)
|
||||
}
|
||||
|
||||
private func remove(_ task: SessionDataTask) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
guard let url = task.originalURL else {
|
||||
return
|
||||
}
|
||||
task.removeAllCallbacks()
|
||||
tasks[url] = nil
|
||||
}
|
||||
|
||||
private func task(for task: URLSessionTask) -> SessionDataTask? {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
guard let url = task.originalRequest?.url else {
|
||||
return nil
|
||||
}
|
||||
guard let sessionTask = tasks[url] else {
|
||||
return nil
|
||||
}
|
||||
guard sessionTask.task.taskIdentifier == task.taskIdentifier else {
|
||||
return nil
|
||||
}
|
||||
return sessionTask
|
||||
}
|
||||
|
||||
func task(for url: URL) -> SessionDataTask? {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return tasks[url]
|
||||
}
|
||||
|
||||
func cancelAll() {
|
||||
lock.lock()
|
||||
let taskValues = tasks.values
|
||||
lock.unlock()
|
||||
for task in taskValues {
|
||||
task.forceCancel()
|
||||
}
|
||||
}
|
||||
|
||||
func cancel(url: URL) {
|
||||
lock.lock()
|
||||
let task = tasks[url]
|
||||
lock.unlock()
|
||||
task?.forceCancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension SessionDelegate: URLSessionDataDelegate {
|
||||
|
||||
open func urlSession(
|
||||
_ session: URLSession,
|
||||
dataTask: URLSessionDataTask,
|
||||
didReceive response: URLResponse,
|
||||
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
|
||||
{
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
let error = KingfisherError.responseError(reason: .invalidURLResponse(response: response))
|
||||
onCompleted(task: dataTask, result: .failure(error))
|
||||
completionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
|
||||
let httpStatusCode = httpResponse.statusCode
|
||||
guard onValidStatusCode.call(httpStatusCode) == true else {
|
||||
let error = KingfisherError.responseError(reason: .invalidHTTPStatusCode(response: httpResponse))
|
||||
onCompleted(task: dataTask, result: .failure(error))
|
||||
completionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
|
||||
let inspectedHandler: (URLSession.ResponseDisposition) -> Void = { disposition in
|
||||
if disposition == .cancel {
|
||||
let error = KingfisherError.responseError(reason: .cancelledByDelegate(response: response))
|
||||
self.onCompleted(task: dataTask, result: .failure(error))
|
||||
}
|
||||
completionHandler(disposition)
|
||||
}
|
||||
onResponseReceived.call((response, inspectedHandler))
|
||||
}
|
||||
|
||||
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
||||
guard let task = self.task(for: dataTask) else {
|
||||
return
|
||||
}
|
||||
|
||||
task.didReceiveData(data)
|
||||
|
||||
task.callbacks.forEach { callback in
|
||||
callback.options.onDataReceived?.forEach { sideEffect in
|
||||
sideEffect.onDataReceived(session, task: task, data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
guard let sessionTask = self.task(for: task) else { return }
|
||||
|
||||
if let url = sessionTask.originalURL {
|
||||
let result: Result<URLResponse, KingfisherError>
|
||||
if let error = error {
|
||||
result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error)))
|
||||
} else if let response = task.response {
|
||||
result = .success(response)
|
||||
} else {
|
||||
result = .failure(KingfisherError.responseError(reason: .noURLResponse(task: sessionTask)))
|
||||
}
|
||||
onDownloadingFinished.call((url, result))
|
||||
}
|
||||
|
||||
let result: Result<(Data, URLResponse?), KingfisherError>
|
||||
if let error = error {
|
||||
result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error)))
|
||||
} else {
|
||||
if let data = onDidDownloadData.call(sessionTask) {
|
||||
result = .success((data, task.response))
|
||||
} else {
|
||||
result = .failure(KingfisherError.responseError(reason: .dataModifyingFailed(task: sessionTask)))
|
||||
}
|
||||
}
|
||||
onCompleted(task: task, result: result)
|
||||
}
|
||||
|
||||
open func urlSession(
|
||||
_ session: URLSession,
|
||||
didReceive challenge: URLAuthenticationChallenge,
|
||||
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
||||
{
|
||||
onReceiveSessionChallenge.call((session, challenge, completionHandler))
|
||||
}
|
||||
|
||||
open func urlSession(
|
||||
_ session: URLSession,
|
||||
task: URLSessionTask,
|
||||
didReceive challenge: URLAuthenticationChallenge,
|
||||
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
||||
{
|
||||
onReceiveSessionTaskChallenge.call((session, task, challenge, completionHandler))
|
||||
}
|
||||
|
||||
open func urlSession(
|
||||
_ session: URLSession,
|
||||
task: URLSessionTask,
|
||||
willPerformHTTPRedirection response: HTTPURLResponse,
|
||||
newRequest request: URLRequest,
|
||||
completionHandler: @escaping (URLRequest?) -> Void)
|
||||
{
|
||||
guard let sessionDataTask = self.task(for: task),
|
||||
let redirectHandler = Array(sessionDataTask.callbacks).last?.options.redirectHandler else
|
||||
{
|
||||
completionHandler(request)
|
||||
return
|
||||
}
|
||||
|
||||
redirectHandler.handleHTTPRedirection(
|
||||
for: sessionDataTask,
|
||||
response: response,
|
||||
newRequest: request,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
private func onCompleted(task: URLSessionTask, result: Result<(Data, URLResponse?), KingfisherError>) {
|
||||
guard let sessionTask = self.task(for: task) else {
|
||||
return
|
||||
}
|
||||
sessionTask.onTaskDone.call((result, sessionTask.callbacks))
|
||||
remove(sessionTask)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user