Explorar o código

Convert some methods in downloader to async

onevcat %!s(int64=2) %!d(string=hai) anos
pai
achega
7b5150e8d7

+ 6 - 2
Sources/General/KingfisherManager.swift

@@ -803,8 +803,12 @@ extension KingfisherManager {
                         continuation.resume(with: result)
                         continuation.resume(with: result)
                     }
                     }
                 )
                 )
-                Task {
-                    await task.setTask(downloadTask)
+                if Task.isCancelled {
+                    downloadTask?.cancel()
+                } else {
+                    Task {
+                        await task.setTask(downloadTask)
+                    }
                 }
                 }
             }
             }
         } onCancel: {
         } onCancel: {

+ 68 - 0
Sources/Networking/ImageDownloader.swift

@@ -472,6 +472,74 @@ open class ImageDownloader {
     }
     }
 }
 }
 
 
+// Concurrency
+extension ImageDownloader {
+    /// 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`.
+    /// - Returns: The image loading result.
+    public func downloadImage(
+        with url: URL,
+        options: KingfisherParsedOptionsInfo
+    ) async throws -> ImageLoadingResult {
+        let task = CancellationDownloadTask()
+        return try await withTaskCancellationHandler {
+            try await withCheckedThrowingContinuation { continuation in
+                let downloadTask = downloadImage(with: url, options: options) { result in
+                    continuation.resume(with: result)
+                }
+                if Task.isCancelled {
+                    downloadTask?.cancel()
+                } else {
+                    Task {
+                        await task.setTask(downloadTask)
+                    }
+                }
+            }
+        } onCancel: {
+            Task {
+                await task.task?.cancel()
+            }
+        }
+    }
+    
+    /// 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.
+    /// - Returns: The image loading result.
+    public func downloadImage(
+        with url: URL,
+        options: KingfisherOptionsInfo? = nil,
+        progressBlock: DownloadProgressBlock? = nil
+    ) async throws -> ImageLoadingResult
+    {
+        var info = KingfisherParsedOptionsInfo(options)
+        if let block = progressBlock {
+            info.onDataReceived = (info.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
+        }
+        return try await downloadImage(with: url, options: info)
+    }
+    
+    /// Downloads an image with a URL and option.
+    ///
+    /// - Parameters:
+    ///   - url: Target URL.
+    ///   - options: The options could control download behavior. See `KingfisherOptionsInfo`.
+    /// - Returns: The image loading result.
+    public func downloadImage(
+        with url: URL,
+        options: KingfisherOptionsInfo? = nil
+    ) async throws -> ImageLoadingResult
+    {
+        return try await downloadImage(with: url, options: KingfisherParsedOptionsInfo(options))
+    }
+}
+
 // MARK: Cancelling Task
 // MARK: Cancelling Task
 extension ImageDownloader {
 extension ImageDownloader {
 
 

+ 33 - 1
Tests/KingfisherTests/ImageDownloaderTests.swift

@@ -67,6 +67,14 @@ class ImageDownloaderTests: XCTestCase {
         waitForExpectations(timeout: 3, handler: nil)
         waitForExpectations(timeout: 3, handler: nil)
     }
     }
     
     
+    func testDownloadAnImageAsync() async throws {
+        let url = testURLs[0]
+        stub(url, data: testImageData)
+        
+        let result = try await downloader.downloadImage(with: url, options: .empty)
+        XCTAssertEqual(result.originalData, testImageData)
+    }
+    
     func testDownloadMultipleImages() {
     func testDownloadMultipleImages() {
         let exp = expectation(description: #function)
         let exp = expectation(description: #function)
         let group = DispatchGroup()
         let group = DispatchGroup()
@@ -84,6 +92,21 @@ class ImageDownloaderTests: XCTestCase {
         waitForExpectations(timeout: 3, handler: nil)
         waitForExpectations(timeout: 3, handler: nil)
     }
     }
     
     
+    func testDownloadMultipleImagesAsync() async throws {
+        try await withThrowingTaskGroup(of: ImageLoadingResult.self) { group in
+            for url in testURLs {
+                stub(url, data: testImageData)
+                group.addTask {
+                    try await self.downloader.downloadImage(with: url)
+                }
+            }
+            
+            for try await result in group {
+                XCTAssertEqual(result.originalData, testImageData)
+            }
+        }
+    }
+    
     func testDownloadAnImageWithMultipleCallback() {
     func testDownloadAnImageWithMultipleCallback() {
         let exp = expectation(description: #function)
         let exp = expectation(description: #function)
         
         
@@ -136,7 +159,6 @@ class ImageDownloaderTests: XCTestCase {
             downloadTaskCalled = true
             downloadTaskCalled = true
         }
         }
 
 
-
         let someURL = URL(string: "some_strage_url")!
         let someURL = URL(string: "some_strage_url")!
         let task = downloader.downloadImage(with: someURL, options: [.requestModifier(asyncModifier)]) { result in
         let task = downloader.downloadImage(with: someURL, options: [.requestModifier(asyncModifier)]) { result in
             XCTAssertNotNil(result.value)
             XCTAssertNotNil(result.value)
@@ -255,6 +277,16 @@ class ImageDownloaderTests: XCTestCase {
         
         
         waitForExpectations(timeout: 3, handler: nil)
         waitForExpectations(timeout: 3, handler: nil)
     }
     }
+    
+    func testCancelDownloadTaskAsync() async throws {
+        let url = testURLs[0]
+        let stub = delayedStub(url, data: testImageData, length: 123)
+        
+        let checker = CallingChecker()
+        try await checker.checkCancelBehavior(stub: stub) {
+            _ = try await self.downloader.downloadImage(with: url)
+        }
+    }
 
 
     func testCancelOneDownloadTask() {
     func testCancelOneDownloadTask() {
         let exp = expectation(description: #function)
         let exp = expectation(description: #function)

+ 29 - 21
Tests/KingfisherTests/KingfisherManagerTests.swift

@@ -27,6 +27,33 @@
 import XCTest
 import XCTest
 @testable import Kingfisher
 @testable import Kingfisher
 
 
+actor CallingChecker {
+    var called = false
+    func mark() {
+        called = true
+    }
+    
+    func checkCancelBehavior(
+        stub: LSStubResponseDSL,
+        block: @escaping () async throws -> Void
+    ) async throws {
+        let task = Task {
+            do {
+                _ = try await block()
+                XCTFail()
+            } catch {
+                mark()
+                XCTAssertTrue((error as! KingfisherError).isTaskCancelled)
+            }
+        }
+        try await Task.sleep(nanoseconds: NSEC_PER_SEC / 10)
+        task.cancel()
+        _ = stub.go()
+        try await Task.sleep(nanoseconds: NSEC_PER_SEC / 10)
+        XCTAssertTrue(called)
+    }
+}
+
 class KingfisherManagerTests: XCTestCase {
 class KingfisherManagerTests: XCTestCase {
     
     
     var manager: KingfisherManager!
     var manager: KingfisherManager!
@@ -193,33 +220,14 @@ class KingfisherManagerTests: XCTestCase {
         waitForExpectations(timeout: 3, handler: nil)
         waitForExpectations(timeout: 3, handler: nil)
     }
     }
     
     
-    actor CallingChecker {
-        var called = false
-        func mark() {
-            called = true
-        }
-    }
-    
     func testRetrieveImageCancelAsync() async throws {
     func testRetrieveImageCancelAsync() async throws {
         let url = testURLs[0]
         let url = testURLs[0]
         let stub = delayedStub(url, data: testImageData, length: 123)
         let stub = delayedStub(url, data: testImageData, length: 123)
 
 
         let checker = CallingChecker()
         let checker = CallingChecker()
-        let task = Task {
-            do {
-                _ = try await manager.retrieveImage(with: url)
-                XCTFail()
-            } catch {
-                await checker.mark()
-                XCTAssertTrue((error as! KingfisherError).isTaskCancelled)
-            }
+        try await checker.checkCancelBehavior(stub: stub) {
+            _ = try await self.manager.retrieveImage(with: url)
         }
         }
-        try await Task.sleep(nanoseconds: NSEC_PER_SEC / 10)
-        task.cancel()
-         _ = stub.go()
-        try await Task.sleep(nanoseconds: NSEC_PER_SEC / 10)
-        let catchCalled = await checker.called
-        XCTAssertTrue(catchCalled)
     }
     }
     
     
     func testSuccessCompletionHandlerRunningOnMainQueueDefaultly() {
     func testSuccessCompletionHandlerRunningOnMainQueueDefaultly() {