瀏覽代碼

Use Result based callback parameter

onevcat 7 年之前
父節點
當前提交
d3d6bef2d5

+ 10 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -113,6 +113,10 @@
 		D12E0C871C47F7AF00AC98AD /* KingfisherOptionsInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12E0C4B1C47F23500AC98AD /* KingfisherOptionsInfoTests.swift */; };
 		D12E0C891C47F7B700AC98AD /* KingfisherTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12E0C4C1C47F23500AC98AD /* KingfisherTestHelper.swift */; };
 		D12E0C8A1C47F7C000AC98AD /* dancing-banana.gif in Resources */ = {isa = PBXBuildFile; fileRef = D12E0C441C47F23500AC98AD /* dancing-banana.gif */; };
+		D13646742165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
+		D13646752165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
+		D13646762165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
+		D13646772165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
 		D1A37BC6215D2DBA009B39B7 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB6B6215D2BB50013BA68 /* ImageCache.swift */; };
 		D1A37BC7215D2DBA009B39B7 /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB6B7215D2BB50013BA68 /* CacheSerializer.swift */; };
 		D1A37BC8215D2DBA009B39B7 /* FormatIndicatedCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB6B8215D2BB50013BA68 /* FormatIndicatedCacheSerializer.swift */; };
@@ -254,6 +258,7 @@
 		D12E0C4D1C47F23500AC98AD /* KingfisherTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KingfisherTests-Bridging-Header.h"; sourceTree = "<group>"; };
 		D12E0C4E1C47F23500AC98AD /* UIButtonExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIButtonExtensionTests.swift; sourceTree = "<group>"; };
 		D12E0C5F1C47F24800AC98AD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		D13646732165A1A100A33652 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
 		D13F49D61BEDA67C00CE335D /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		D16799EB1C4E74460020FD12 /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		D1A37BDD215D34E8009B39B7 /* ImageDrawing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDrawing.swift; sourceTree = "<group>"; };
@@ -441,6 +446,7 @@
 		D12AB6B9215D2BB50013BA68 /* Utility */ = {
 			isa = PBXGroup;
 			children = (
+				D13646732165A1A100A33652 /* Result.swift */,
 				D12AB6BA215D2BB50013BA68 /* ThreadHelper.swift */,
 				D12AB6BB215D2BB50013BA68 /* Box.swift */,
 				D12AB6BC215D2BB50013BA68 /* String+MD5.swift */,
@@ -936,6 +942,7 @@
 				D1A37BC7215D2DBA009B39B7 /* CacheSerializer.swift in Sources */,
 				D1A37BC8215D2DBA009B39B7 /* FormatIndicatedCacheSerializer.swift in Sources */,
 				D1A37BC9215D2DBA009B39B7 /* ImageView+Kingfisher.swift in Sources */,
+				D13646762165A1A100A33652 /* Result.swift in Sources */,
 				D1A37BCA215D2DBA009B39B7 /* NSButton+Kingfisher.swift in Sources */,
 				D1A37BCB215D2DBA009B39B7 /* Kingfisher.swift in Sources */,
 				D1A37BCC215D2DBA009B39B7 /* KingfisherError.swift in Sources */,
@@ -1025,6 +1032,7 @@
 				D12AB6F5215D2BB50013BA68 /* ImageView+Kingfisher.swift in Sources */,
 				D12AB6FD215D2BB50013BA68 /* UIButton+Kingfisher.swift in Sources */,
 				D12AB6E9215D2BB50013BA68 /* GIFAnimatedImage.swift in Sources */,
+				D13646752165A1A100A33652 /* Result.swift in Sources */,
 				D12AB6E1215D2BB50013BA68 /* Filter.swift in Sources */,
 				D12AB6C5215D2BB50013BA68 /* Resource.swift in Sources */,
 				D1A37BDF215D34E8009B39B7 /* ImageDrawing.swift in Sources */,
@@ -1053,6 +1061,7 @@
 				D12AB703215D2BB50013BA68 /* WKInterfaceImage+Kingfisher.swift in Sources */,
 				D12AB70F215D2BB50013BA68 /* KingfisherManager.swift in Sources */,
 				D1A37BF0215D375F009B39B7 /* Deprecated.swift in Sources */,
+				D13646772165A1A100A33652 /* Result.swift in Sources */,
 				D12AB6C3215D2BB50013BA68 /* RequestModifier.swift in Sources */,
 				D12AB6CB215D2BB50013BA68 /* ImageDownloader.swift in Sources */,
 				D12AB717215D2BB50013BA68 /* ImageCache.swift in Sources */,
@@ -1094,6 +1103,7 @@
 				D12AB6F4215D2BB50013BA68 /* ImageView+Kingfisher.swift in Sources */,
 				D12AB6FC215D2BB50013BA68 /* UIButton+Kingfisher.swift in Sources */,
 				D12AB6E8215D2BB50013BA68 /* GIFAnimatedImage.swift in Sources */,
+				D13646742165A1A100A33652 /* Result.swift in Sources */,
 				D12AB6E0215D2BB50013BA68 /* Filter.swift in Sources */,
 				D12AB6C4215D2BB50013BA68 /* Resource.swift in Sources */,
 				D1A37BDE215D34E8009B39B7 /* ImageDrawing.swift in Sources */,

+ 1 - 1
Sources/Extensions/WKInterfaceImage+Kingfisher.swift

@@ -56,7 +56,7 @@ extension KingfisherClass where Base: WKInterfaceImage {
 
         let task = KingfisherManager.shared.retrieveImage(
             with: resource,
-            options: KingfisherManager.shared.defaultOptions + (options ?? KingfisherEmptyOptionsInfo),
+            options: KingfisherManager.shared.defaultOptions + (options ?? .empty),
             progressBlock: { receivedSize, totalSize in
                 guard resource.downloadURL == self.webURL else {
                     return

+ 37 - 0
Sources/General/Deprecated.swift

@@ -61,3 +61,40 @@ extension KingfisherClass where Base: Image {
         return animatedImage(data: data, options: options)
     }
 }
+
+@available(*, deprecated, message: "Use `Result<ImageResult>` based callback instead")
+public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void)
+
+extension RetrieveImageTask {
+    @available(*, deprecated, message: "RetrieveImageTask.empty will be removed soon. Use `nil` to represnt a no task.")
+    public static let empty = RetrieveImageTask()
+}
+
+extension KingfisherManager {
+    /// Get an image with resource.
+    /// If `.empty` is used as `options`, Kingfisher will seek the image in memory and disk first.
+    /// If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`.
+    /// These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more.
+    ///
+    /// - Parameters:
+    ///   - resource: Resource object contains information such as `cacheKey` and `downloadURL`.
+    ///   - options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
+    ///   - progressBlock: Called every time downloaded data changed. This could be used as a progress UI.
+    ///   - completionHandler: Called when the whole retrieving process finished.
+    /// - Returns: A `RetrieveImageTask` task object. You can use this object to cancel the task.
+    @available(*, deprecated, message: "Use `Result` based callback instead.")
+    @discardableResult
+    public func retrieveImage(with resource: Resource,
+                              options: KingfisherOptionsInfo?,
+                              progressBlock: DownloadProgressBlock?,
+                              completionHandler: CompletionHandler?) -> RetrieveImageTask
+    {
+        return retrieveImage(with: resource, options: options, progressBlock: progressBlock) {
+            result in
+            switch result {
+            case .success(let value): completionHandler?(value.image, nil, value.cacheType, value.imageURL)
+            case .failure(let error): completionHandler?(nil, error as NSError, .none, nil)
+            }
+        }
+    }
+}

+ 3 - 0
Sources/General/KingfisherError.swift

@@ -26,5 +26,8 @@
 
 import Foundation
 
+/// Error domain of Kingfisher
+public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error"
+
 struct KingfisherPlaceholderError: Error {
 }

+ 40 - 53
Sources/General/KingfisherManager.swift

@@ -31,19 +31,24 @@ import UIKit
 #endif
 
 public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> Void)
-public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void)
+public typealias ResultCompletionHandler = ((Result<RetrieveImageResult>) -> Void)
+
+public struct RetrieveImageResult {
+    public let image: Image
+    public let cacheType: CacheType
+    public let imageURL: URL
+}
 
 /// RetrieveImageTask represents a task of image retrieving process.
 /// It contains an async task of getting image from disk and from network.
 public final class RetrieveImageTask {
     
-    public static let empty = RetrieveImageTask()
-    
     // If task is canceled before the download task started (which means the `downloadTask` is nil),
     // the download task should not begin.
     var cancelledBeforeDownloadStarting: Bool = false
     
-    /// The network retrieve task in this image task.
+    /// The network retrieve task in this image task if exist. If the task does not need download (since the requested
+    /// image is found in cache), or the download is already finished, it is `nil`,
     public var downloadTask: RetrieveImageDownloadTask?
     
     /**
@@ -58,9 +63,6 @@ public final class RetrieveImageTask {
     }
 }
 
-/// Error domain of Kingfisher
-public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error"
-
 /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache.
 /// You can use this class to retrieve an image via a specified URL from web or cache.
 public class KingfisherManager {
@@ -75,19 +77,20 @@ public class KingfisherManager {
     public var downloader: ImageDownloader
     
     /// Default options used by the manager. This option will be used in 
-    /// Kingfisher manager related methods, including all image view and 
-    /// button extension methods. You can also passing the options per image by 
-    /// sending an `options` parameter to Kingfisher's APIs, the per image option 
-    /// will overwrite the default ones if exist.
+    /// Kingfisher manager related methods, and all image view and
+    /// button extension methods. You can also passing other options for each image task by
+    /// sending an `options` parameter to Kingfisher's APIs, the per image options
+    /// will overwrite the default ones, if exist in both.
     ///
     /// - Note: This option will not be applied to independent using of `ImageDownloader` or `ImageCache`.
     public var defaultOptions = KingfisherOptionsInfo.empty
     
+    // Use `defaultOptions` to overwrite the `downloader` and `cache`.
     var currentDefaultOptions: KingfisherOptionsInfo {
         return [.downloader(downloader), .targetCache(cache)] + defaultOptions
     }
 
-    fileprivate let processQueue: DispatchQueue
+    private let processQueue: DispatchQueue
     
     convenience init() {
         self.init(downloader: .default, cache: .default)
@@ -101,24 +104,11 @@ public class KingfisherManager {
         processQueue = DispatchQueue(label: processQueueName, attributes: .concurrent)
     }
     
-    /**
-    Get an image with resource.
-    If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first.
-    If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`.
-    These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more.
-    
-    - parameter resource:          Resource object contains information such as `cacheKey` and `downloadURL`.
-    - parameter options:           A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
-    - parameter progressBlock:     Called every time downloaded data changed. This could be used as a progress UI.
-    - parameter completionHandler: Called when the whole retrieving process finished.
-    
-    - returns: A `RetrieveImageTask` task object. You can use this object to cancel the task.
-    */
     @discardableResult
     public func retrieveImage(with resource: Resource,
-        options: KingfisherOptionsInfo?,
-        progressBlock: DownloadProgressBlock?,
-        completionHandler: CompletionHandler?) -> RetrieveImageTask
+                              options: KingfisherOptionsInfo?,
+                              progressBlock: DownloadProgressBlock?,
+                              completionHandler: ResultCompletionHandler?) -> RetrieveImageTask
     {
         let task = RetrieveImageTask()
         let options = currentDefaultOptions + (options ?? .empty)
@@ -148,7 +138,7 @@ public class KingfisherManager {
                              forKey key: String,
                       retrieveImageTask: RetrieveImageTask,
                           progressBlock: DownloadProgressBlock?,
-                      completionHandler: CompletionHandler?,
+                      completionHandler: ResultCompletionHandler?,
                                 options: KingfisherOptionsInfo) -> RetrieveImageDownloadTask?
     {
         let downloader = options.downloader ?? self.downloader
@@ -158,17 +148,7 @@ public class KingfisherManager {
                 progressBlock?(receivedSize, totalSize)
             },
             completionHandler: { image, error, imageURL, originalData in
-
                 let targetCache = options.targetCache ?? self.cache
-                if let error = error, error.code == KingfisherError.notModified.rawValue {
-                    // Not modified. Try to find the image from cache.
-                    // (The image should be in cache. It should be guaranteed by the framework users.)
-                    targetCache.retrieveImage(forKey: key, options: options) { cacheImage, cacheType in
-                        completionHandler?(cacheImage, nil, cacheType, url)
-                    }
-                    return
-                }
-                
                 if let image = image, let originalData = originalData {
                     targetCache.store(image,
                                       original: originalData,
@@ -176,10 +156,12 @@ public class KingfisherManager {
                                       processorIdentifier:options.processor.identifier,
                                       cacheSerializer: options.cacheSerializer,
                                       toDisk: !options.cacheMemoryOnly) {
+                                        
                                         guard options.waitForCache else { return }
                                         
                                         let cacheType = targetCache.imageCachedType(forKey: key, processorIdentifier: options.processor.identifier)
-                                        completionHandler?(image, nil, cacheType, url)
+                                        let result = RetrieveImageResult(image: image, cacheType: cacheType, imageURL: url)
+                                        completionHandler?(.success(result))
                     }
                     
                     if options.cacheOriginalImage && options.processor != DefaultImageProcessor.default {
@@ -200,7 +182,12 @@ public class KingfisherManager {
                 }
 
                 if options.waitForCache == false {
-                    completionHandler?(image, error, .none, url)
+                    if let error = error {
+                        completionHandler?(.failure(error))
+                    } else {
+                        let result = RetrieveImageResult(image: image!, cacheType: .none, imageURL: url)
+                        completionHandler?(.success(result))
+                    }
                 }
             })
     }
@@ -209,18 +196,13 @@ public class KingfisherManager {
                                        with url: URL,
                               retrieveImageTask: RetrieveImageTask,
                                   progressBlock: DownloadProgressBlock?,
-                              completionHandler: CompletionHandler?,
+                              completionHandler: ResultCompletionHandler?,
                                         options: KingfisherOptionsInfo)
     {
-
-        let diskTaskCompletionHandler: CompletionHandler = { image, error, cacheType, imageURL in
-            completionHandler?(image, error, cacheType, imageURL)
-        }
-        
         func handleNoCache() {
             if options.onlyFromCache {
                 let error = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notCached.rawValue, userInfo: nil)
-                diskTaskCompletionHandler(nil, error, .none, url)
+                completionHandler?(.failure(error))
                 return
             }
             downloadAndCacheImage(
@@ -228,7 +210,7 @@ public class KingfisherManager {
                 forKey: key,
                 retrieveImageTask: retrieveImageTask,
                 progressBlock: progressBlock,
-                completionHandler: diskTaskCompletionHandler,
+                completionHandler: completionHandler,
                 options: options)
             
         }
@@ -239,7 +221,8 @@ public class KingfisherManager {
         targetCache.retrieveImage(forKey: key, options: options) { image, cacheType in
             // If found, we could finish now.
             if image != nil {
-                diskTaskCompletionHandler(image, nil, cacheType, url)
+                let result = RetrieveImageResult(image: image!, cacheType: cacheType, imageURL: url)
+                completionHandler?(.success(result))
                 return
             }
             
@@ -265,7 +248,8 @@ public class KingfisherManager {
                 processQueue.async {
                     guard let processedImage = processor.process(item: .image(image), options: options) else {
                         options.callbackDispatchQueue.safeAsync {
-                            diskTaskCompletionHandler(nil, nil, .none, url)
+                            #warning("handle processor error")
+//                            diskTaskCompletionHandler(nil, nil, .none, url)
                         }
                         return
                     }
@@ -275,17 +259,20 @@ public class KingfisherManager {
                                       processorIdentifier:options.processor.identifier,
                                       cacheSerializer: options.cacheSerializer,
                                       toDisk: !options.cacheMemoryOnly) {
+                                        
                                         guard options.waitForCache else { return }
 
                                         let cacheType = targetCache.imageCachedType(forKey: key, processorIdentifier: options.processor.identifier)
                                         options.callbackDispatchQueue.safeAsync {
-                                            diskTaskCompletionHandler(processedImage, nil, cacheType, url)
+                                            let result = RetrieveImageResult(image: processedImage, cacheType: cacheType, imageURL: url)
+                                            completionHandler?(.success(result))
                                         }
                     }
 
                     if options.waitForCache == false {
                         options.callbackDispatchQueue.safeAsync {
-                            diskTaskCompletionHandler(processedImage, nil, .none, url)
+                            let result = RetrieveImageResult(image: processedImage, cacheType: .none, imageURL: url)
+                            completionHandler?(.success(result))
                         }
                     }
                 }

+ 2 - 2
Sources/Networking/ImagePrefetcher.swift

@@ -197,9 +197,9 @@ public class ImagePrefetcher {
     
     func downloadAndCache(_ resource: Resource) {
 
-        let downloadTaskCompletionHandler: CompletionHandler = { image, error, _, _ in
+        let downloadTaskCompletionHandler: ResultCompletionHandler = { result in
             self.tasks.removeValue(forKey: resource.downloadURL)
-            if let _ = error {
+            if let _ = result.error {
                 self.failedResources.append(resource)
             } else {
                 self.completedResources.append(resource)

+ 79 - 0
Sources/Utility/Result.swift

@@ -0,0 +1,79 @@
+//
+//  Result.swift
+//  Kingfisher
+//
+//  Created by onevcat on 2018/09/22.
+//
+//  Copyright (c) 2018 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 result of some operation, whether it is successful or an error happens.
+///
+/// - success: The operation is successful and an associated value could be provided.
+/// - failure: An error happens during the operation.
+public enum Result<Value> {
+    case success(Value)
+    case failure(Error)
+    
+    /// Returns `true` if the result is a success, `false` otherwise.
+    public var isSuccess: Bool {
+        if case .success = self {
+            return true
+        }
+        return false
+    }
+    
+    /// Returns the associated value if the result is a success, `nil` otherwise.
+    public var value: Value? {
+        if case .success(let v) = self {
+            return v
+        }
+        return nil
+    }
+    
+    /// Returns `true` if the result is a failure, `false` otherwise.
+    public var failure: Bool {
+        return !isSuccess
+    }
+    
+    /// Returns the associated error value if the result is a failure, `nil` otherwise.
+    public var error: Error? {
+        if case .failure(let e) = self {
+            return e
+        }
+        return nil
+    }
+    
+    /// Map over the `Result` value. If it was a `.success`, `transform` closure will be applied to associated value
+    /// and a new `.success` with transformed value will be returned. If it was a `.failure`, the same error will be
+    /// returned.
+    ///
+    /// - Parameter transform: A closure that takes the success value of the instance.
+    /// - Returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the
+    ///            same failure.
+    public func map<T>(_ transform: (Value) -> T) -> Result<T> {
+        switch self {
+        case .success(let value): return .success(transform(value))
+        case .failure(let error): return .failure(error)
+        }
+    }
+}

+ 4 - 39
Tests/KingfisherTests/KingfisherManagerTests.swift

@@ -149,41 +149,6 @@ class KingfisherManagerTests: XCTestCase {
         waitForExpectations(timeout: 5, handler: nil)
     }
     
-    func testRetrieveImageNotModified() {
-        let expectation = self.expectation(description: "wait for downloading image")
-        let URLString = testKeys[0]
-        _ = stubRequest("GET", URLString).andReturn(200)?.withBody(testImageData)
-        
-        let url = URL(string: URLString)!
-        
-        manager.retrieveImage(with: url, options: nil, progressBlock: nil) {
-            image, error, cacheType, imageURL in
-            XCTAssertNotNil(image)
-            XCTAssertEqual(cacheType, CacheType.none)
-            
-            self.manager.cache.clearMemoryCache()
-            
-            _ = stubRequest("GET", URLString).andReturn(304)?.withBody("12345" as NSString)
-            
-            var progressCalled = false
-            
-            self.manager.retrieveImage(with: url, options: [.forceRefresh], progressBlock: {
-                _, _ in
-                progressCalled = true
-            }) {
-                image, error, cacheType, imageURL in
-                XCTAssertNotNil(image)
-                XCTAssertEqual(cacheType, CacheType.disk)
-                
-                XCTAssertTrue(progressCalled, "The progress callback should be called at least once since network connecting happens.")
-                
-                expectation.fulfill()
-            }
-        }
-        
-        waitForExpectations(timeout: 5, handler: nil)
-    }
-    
     func testSuccessCompletionHandlerRunningOnMainQueueDefaultly() {
         let progressExpectation = expectation(description: "progressBlock running on main queue")
         let completionExpectation = expectation(description: "completionHandler running on main queue")
@@ -295,9 +260,9 @@ class KingfisherManagerTests: XCTestCase {
         let url = URL(string: URLString)!
         
         manager.defaultOptions = [.scaleFactor(2)]
-        manager.retrieveImage(with: url, options: nil, progressBlock: nil, completionHandler: { image, _, _, _ in
+        manager.retrieveImage(with: url, options: nil, progressBlock: nil, completionHandler: { result in
             #if !os(macOS)
-            XCTAssertEqual(image!.scale, 2.0)
+            XCTAssertEqual(result.value!.image.scale, 2.0)
             #endif
             expectation.fulfill()
         })
@@ -314,7 +279,7 @@ class KingfisherManagerTests: XCTestCase {
         let p = SimpleProcessor()
         let options: KingfisherOptionsInfo = [.processor(p), .cacheOriginalImage]
         self.manager.downloadAndCacheImage(with: url, forKey: URLString, retrieveImageTask: RetrieveImageTask(), progressBlock: nil, completionHandler: {
-            (image, error, cacheType, url) in
+            result in
             delay(0.1) {
                 var imageCached = self.manager.cache.imageCachedType(forKey: URLString, processorIdentifier: p.identifier)
                 var originalCached = self.manager.cache.imageCachedType(forKey: URLString)
@@ -346,7 +311,7 @@ class KingfisherManagerTests: XCTestCase {
         let p = SimpleProcessor()
         let options: KingfisherOptionsInfo = [.processor(p)]
         manager.downloadAndCacheImage(with: url, forKey: URLString, retrieveImageTask: RetrieveImageTask(), progressBlock: nil, completionHandler: {
-            (image, error, cacheType, url) in
+            result in
             delay(0.1) {
                 var imageCached = self.manager.cache.imageCachedType(forKey: URLString, processorIdentifier: p.identifier)
                 var originalCached = self.manager.cache.imageCachedType(forKey: URLString)