From 5b5db5a181b24a4b2df4dbb557b8a509ff74eb6e Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 15 Oct 2023 12:31:36 +0900 Subject: [PATCH] Implement async func for AsyncImageDownloadRequestModifier type --- Sources/Networking/ImageDownloader.swift | 19 +++++-- Sources/Networking/RequestModifier.swift | 55 +++++++++++++------ .../ImageDownloaderTests.swift | 8 +-- 3 files changed, 57 insertions(+), 25 deletions(-) diff --git a/Sources/Networking/ImageDownloader.swift b/Sources/Networking/ImageDownloader.swift index 6827af88d..78ab10fd8 100644 --- a/Sources/Networking/ImageDownloader.swift +++ b/Sources/Networking/ImageDownloader.swift @@ -248,7 +248,7 @@ open class ImageDownloader { done: @escaping ((Result) -> Void) ) { - func checkRequestAndDone(r: URLRequest) { + @Sendable func checkRequestAndDone(r: URLRequest) { // There is a possibility that request modifier changed the url to `nil` or empty. // In this case, throw an error. @@ -269,13 +269,24 @@ open class ImageDownloader { if let requestModifier = options.requestModifier { // Modifies request before sending. - requestModifier.modified(for: request) { result in - guard let finalRequest = result else { + // FIXME: A temporary solution for keep the sync `ImageDownloadRequestModifier` behavior as before. + // We should be able to combine two cases once the full async support can be introduced to Kingfisher. + if let m = requestModifier as? ImageDownloadRequestModifier { + guard let result = m.modified(for: request) else { done(.failure(KingfisherError.requestError(reason: .emptyRequest))) return } - checkRequestAndDone(r: finalRequest) + checkRequestAndDone(r: result) + } else { + Task { [request] in + guard let result = await requestModifier.modified(for: request) else { + done(.failure(KingfisherError.requestError(reason: .emptyRequest))) + return + } + checkRequestAndDone(r: result) + } } + } else { checkRequestAndDone(r: request) } diff --git a/Sources/Networking/RequestModifier.swift b/Sources/Networking/RequestModifier.swift index a1d972df3..c6d264f89 100644 --- a/Sources/Networking/RequestModifier.swift +++ b/Sources/Networking/RequestModifier.swift @@ -26,31 +26,52 @@ import Foundation -/// Represents and wraps a method for modifying request before an image download request starts in an asynchronous way. +/// Represents and wraps a method for modifying a request before an image download request starts asynchronously. +/// +/// Usually, you pass an ``AsyncImageDownloadRequestModifier`` instance as the associated value of +/// ``KingfisherOptionsInfoItem/requestModifier`` and use it as the `options` parameter in related methods. +/// +/// For example, the code below defines a modifier to add a header field and its value to the request. +/// +/// ```swift +/// class HeaderFieldModifier: AsyncImageDownloadRequestModifier { +/// var onDownloadTaskStarted: ((DownloadTask?) -> Void)? = nil +/// func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) { +/// var r = request +/// r.setValue("value", forHTTPHeaderField: "key") +/// reportModified(r) +/// } +/// } +/// +/// imageView.kf.setImage(with: url, options: [.requestModifier(HeaderFieldModifier())]) +/// ``` +/// 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. + /// This method will be called just before the `request` is sent. /// - /// Usually, you pass an `AsyncImageDownloadRequestModifier` as the associated value of - /// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods. + /// This is the last chance to modify the image download request. You can modify the request for some customizing + /// purposes, such as adding an auth token to the header, performing basic HTTP auth, or something like URL mapping. + /// + /// After making the modification, call the `reportModified` block with the modified request, and the data + /// will be downloaded with this modified request. /// - /// If you do nothing with the input `request` and return it as is, a downloading process will start with it. + /// > If you do nothing with the input `request` and return it as-is, the download process will start with it as the + /// modifier doesn't exist. /// /// - 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) + /// - 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 modification is done. + func modified(for request: URLRequest) async -> URLRequest? - /// A block will be called when the download task started. + /// A block that will be called when the download task starts. + /// + /// If an ``AsyncImageDownloadRequestModifier`` and asynchronous modification occur before the download, the + /// related download method will not return a valid ``DownloadTask`` value. Instead, you can get one from this + /// method. /// - /// 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. + /// User the ``DownloadTask`` value to track the task, or cancel it when you need to. var onDownloadTaskStarted: ((DownloadTask?) -> Void)? { get } } diff --git a/Tests/KingfisherTests/ImageDownloaderTests.swift b/Tests/KingfisherTests/ImageDownloaderTests.swift index 6a223909e..0d481f193 100644 --- a/Tests/KingfisherTests/ImageDownloaderTests.swift +++ b/Tests/KingfisherTests/ImageDownloaderTests.swift @@ -710,11 +710,11 @@ class AsyncURLModifier: AsyncImageDownloadRequestModifier { var url: URL? = nil var onDownloadTaskStarted: ((DownloadTask?) -> Void)? - func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) { + func modified(for request: URLRequest) async -> URLRequest? { var r = request r.url = url - DispatchQueue.main.async { - reportModified(r) - } + // Simulate an async action + try? await Task.sleep(nanoseconds: 1_000_000) + return r } }