Răsfoiți Sursa

Merge pull request #2150 from onevcat/fix/async-request-modifier

Adopt async request modifier
Wei Wang 2 ani în urmă
părinte
comite
89bd8e8103

+ 15 - 4
Sources/Networking/ImageDownloader.swift

@@ -248,7 +248,7 @@ open class ImageDownloader {
         done: @escaping ((Result<DownloadingContext, KingfisherError>) -> 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)
         }

+ 38 - 17
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 }
 }
 

+ 4 - 4
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
     }
 }