Procházet zdrojové kódy

Refactor for button related methods and tests

onevcat před 7 roky
rodič
revize
83cfe18003

+ 1 - 1
Demo/Demo/Kingfisher-watchOS-Demo Extension/InterfaceController.swift

@@ -47,7 +47,7 @@ class InterfaceController: WKInterfaceController {
     
     func refreshImage() {
         let url = URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(currentIndex! + 1).jpg")!
-        interfaceImage.kf.setImage(url)
+        interfaceImage.kf.setImage(with: url)
     }
 
     override func willActivate() {

+ 2 - 2
Kingfisher.xcodeproj/project.pbxproj

@@ -1549,7 +1549,7 @@
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
-				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
@@ -1601,7 +1601,7 @@
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
 				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
-				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";

+ 1 - 9
Sources/Cache/ImageCache.swift

@@ -62,14 +62,7 @@ public enum CacheType {
 }
 
 extension Image: CacheCostCalculatable {
-    public var cacheCost: Int {
-        let pixel = Int(size.width * size.height * scale * scale)
-        guard let cgImage = cgImage else {
-            return pixel * 4 / 1024
-        }
-
-        return pixel * cgImage.bitsPerPixel / 8 / 1024
-    }
+    public var cacheCost: Int { return kf.cost }
 }
 
 extension Data: DataTransformable {
@@ -150,7 +143,6 @@ open class ImageCache {
 
         #warning("Choose a proper init cost limit.")
         memoryStorage = MemoryStorage(config: .init(totalCostLimit: 0))
-        #warning("Choose a proper init cost limit.")
 
         var diskConfig = DiskStorage<Data>.Config(
             name: name,

+ 17 - 22
Sources/Extensions/ImageView+Kingfisher.swift

@@ -31,28 +31,23 @@ import AppKit
 import UIKit
 #endif
 
-// MARK: - Extension methods.
-/**
- *    Set image to use from web.
- */
 extension KingfisherClass where Base: ImageView {
-    /**
-     Set an image with a resource, a placeholder image, options, progress handler and completion handler.
-     
-     - parameter resource:          Resource object contains information such as `cacheKey` and `downloadURL`.
-     - parameter placeholder:       A placeholder image when retrieving the image at URL.
-     - parameter options:           A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
-     - parameter progressBlock:     Called when the image downloading progress gets updated.
-     - parameter completionHandler: Called when the image retrieved and set.
-     
-     - returns: A task represents the retrieving process.
-     
-     - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread.
-     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
-     
-     If `resource` is `nil`, the `placeholder` image will be set and
-     `completionHandler` will be called with both `error` and `image` being `nil`.
-     */
+
+    /// Set an image with a resource, a placeholder image, options, progress handler and completion handler.
+    ///
+    /// Internally, this method will use KingfisherManager to get the requested resource, from either cache
+    /// or network. Since this methos will perform UI changes, you must call this method from the UI thread.
+    /// Both `progressBlock` and `completionHandler` will be also executed in the UI thread.
+    ///
+    /// - Parameters:
+    ///   - resource: The Resource object contains information about the resource.
+    ///   - placeholder: A placeholder to show while retrieving the image from the given `resource`.
+    ///   - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more.
+    ///   - progressBlock: Called when the image downloading progress gets updated. If the response does not contain an
+    ///                    `expectedContentLength`, this block will not be called.
+    ///   - completionHandler: Called when the image retrieved and set finished.
+    /// - Returns: A task represents the image downloading.
+    ///
     @discardableResult
     public func setImage(with resource: Resource?,
                          placeholder: Placeholder? = nil,
@@ -174,7 +169,7 @@ private var placeholderKey: Void?
 private var imageTaskKey: Void?
 
 extension KingfisherClass where Base: ImageView {
-    /// Get the image URL binded to this image view.
+    /// Gets the image URL binded to this image view.
     public private(set) var webURL: URL? {
         get { return getAssociatedObject(base, &lastURLKey) }
         set { setRetainedAssociatedObject(base, &lastURLKey, newValue) }

+ 52 - 67
Sources/Extensions/NSButton+Kingfisher.swift

@@ -27,10 +27,6 @@
 
 import AppKit
 
-// MARK: - Set Images
-/**
- *	Set image to use from web.
- */
 extension KingfisherClass where Base: NSButton {
     /**
      Set an image with a resource, a placeholder image, options, progress handler and completion handler.
@@ -54,11 +50,11 @@ extension KingfisherClass where Base: NSButton {
                          placeholder: Image? = nil,
                          options: KingfisherOptionsInfo? = nil,
                          progressBlock: DownloadProgressBlock? = nil,
-                         completionHandler: ResultCompletionHandler? = nil) -> SessionDataTask?
+                         completionHandler: ((Result<RetrieveImageResult>) -> Void)? = nil) -> DownloadTask?
     {
         guard let resource = resource else {
             base.image = placeholder
-            setWebURL(nil)
+            webURL = nil
             completionHandler?(.failure(KingfisherError2.imageSettingError(reason: .emptyResource)))
             return nil
         }
@@ -68,34 +64,36 @@ extension KingfisherClass where Base: NSButton {
             base.image = placeholder
         }
         
-        setWebURL(resource.downloadURL)
+        webURL = resource.downloadURL
         let task = KingfisherManager.shared.retrieveImage(
             with: resource,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard resource.downloadURL == self.webURL else {
-                    return
-                }
-                if let progressBlock = progressBlock {
-                    progressBlock(receivedSize, totalSize)
-                }
+                guard resource.downloadURL == self.webURL else { return }
+                progressBlock?(receivedSize, totalSize)
             },
-            completionHandler: { [weak base] image, error, cacheType, imageURL in
+            completionHandler: { result in
                 DispatchQueue.main.safeAsync {
-                    guard let strongBase = base, imageURL == self.webURL else {
-                        completionHandler?(image, error, cacheType, imageURL)
+                    guard resource.downloadURL == self.webURL else {
+                        let error = KingfisherError2.imageSettingError(
+                            reason: .notCurrentResource(result: result, resource: resource))
+                        completionHandler?(.failure(error))
                         return
                     }
-                    self.setImageTask(nil)
                     
-                    if image != nil {
-                        strongBase.image = image
+                    self.imageTask = nil
+                    
+                    switch result {
+                    case .success(let value):
+                        self.base.image = value.image
+                        completionHandler?(result)
+                    case .failure:
+                        completionHandler?(result)
                     }
-                    completionHandler?(image, error, cacheType, imageURL)
                 }
             })
         
-        setImageTask(task)
+        imageTask = task
         return task
     }
     
@@ -129,13 +127,13 @@ extension KingfisherClass where Base: NSButton {
                                   placeholder: Image? = nil,
                                   options: KingfisherOptionsInfo? = nil,
                                   progressBlock: DownloadProgressBlock? = nil,
-                                  completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
+                                  completionHandler: ((Result<RetrieveImageResult>) -> Void)? = nil) -> DownloadTask?
     {
         guard let resource = resource else {
             base.alternateImage = placeholder
-            setAlternateWebURL(nil)
-            completionHandler?(nil, nil, .none, nil)
-            return .empty
+            alternateWebURL = nil
+            completionHandler?(.failure(KingfisherError2.imageSettingError(reason: .emptyResource)))
+            return nil
         }
         
         let options = KingfisherManager.shared.defaultOptions + (options ?? .empty)
@@ -143,35 +141,36 @@ extension KingfisherClass where Base: NSButton {
             base.alternateImage = placeholder
         }
         
-        setAlternateWebURL(resource.downloadURL)
+        alternateWebURL = resource.downloadURL
         let task = KingfisherManager.shared.retrieveImage(
             with: resource,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard resource.downloadURL == self.alternateWebURL else {
-                    return
-                }
-                if let progressBlock = progressBlock {
-                    progressBlock(receivedSize, totalSize)
-                }
+                guard resource.downloadURL == self.alternateWebURL else { return }
+                progressBlock?(receivedSize, totalSize)
             },
-            completionHandler: { [weak base] image, error, cacheType, imageURL in
+            completionHandler: { result in
                 DispatchQueue.main.safeAsync {
-                    guard let strongBase = base, imageURL == self.alternateWebURL else {
-                        completionHandler?(image, error, cacheType, imageURL)
+                    guard resource.downloadURL == self.alternateWebURL else {
+                        let error = KingfisherError2.imageSettingError(
+                            reason: .notCurrentResource(result: result, resource: resource))
+                        completionHandler?(.failure(error))
                         return
                     }
-                    self.setAlternateImageTask(nil)
                     
-                    if image != nil {
-                        strongBase.alternateImage = image
-                    }
+                    self.alternateImageTask = nil
                     
-                    completionHandler?(image, error, cacheType, imageURL)
+                    switch result {
+                    case .success(let value):
+                        self.base.alternateImage = value.image
+                        completionHandler?(result)
+                    case .failure:
+                        completionHandler?(result)
+                    }
                 }
             })
         
-        setAlternateImageTask(task)
+        alternateImageTask = task
         return task
     }
     
@@ -192,37 +191,23 @@ private var lastAlternateURLKey: Void?
 private var alternateImageTaskKey: Void?
 
 extension KingfisherClass where Base: NSButton {
-    /// Get the image URL binded to this image view.
-    public var webURL: URL? {
-        return objc_getAssociatedObject(base, &lastURLKey) as? URL
-    }
-    
-    fileprivate func setWebURL(_ url: URL?) {
-        objc_setAssociatedObject(base, &lastURLKey, url, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-    }
-    
-    fileprivate var imageTask: RetrieveImageTask? {
-        return objc_getAssociatedObject(base, &imageTaskKey) as? RetrieveImageTask
-    }
-    
-    fileprivate func setImageTask(_ task: RetrieveImageTask?) {
-        objc_setAssociatedObject(base, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-    }
-    
-    /// Get the alternate image URL binded to this button.
-    public var alternateWebURL: URL? {
-        return objc_getAssociatedObject(base, &lastAlternateURLKey) as? URL
+    public private(set) var webURL: URL? {
+        get { return getAssociatedObject(base, &lastURLKey) }
+        set { setRetainedAssociatedObject(base, &lastURLKey, newValue) }
     }
     
-    fileprivate func setAlternateWebURL(_ url: URL?) {
-        objc_setAssociatedObject(base, &lastAlternateURLKey, url, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+    private var imageTask: DownloadTask? {
+        get { return getAssociatedObject(base, &imageTaskKey) }
+        set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)}
     }
     
-    fileprivate var alternateImageTask: RetrieveImageTask? {
-        return objc_getAssociatedObject(base, &alternateImageTaskKey) as? RetrieveImageTask
+    public private(set) var alternateWebURL: URL? {
+        get { return getAssociatedObject(base, &lastAlternateURLKey) }
+        set { setRetainedAssociatedObject(base, &lastAlternateURLKey, newValue) }
     }
     
-    fileprivate func setAlternateImageTask(_ task: RetrieveImageTask?) {
-        objc_setAssociatedObject(base, &alternateImageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+    private var alternateImageTask: DownloadTask? {
+        get { return getAssociatedObject(base, &alternateImageTaskKey) }
+        set { setRetainedAssociatedObject(base, &alternateImageTaskKey, newValue)}
     }
 }

+ 1 - 3
Sources/Extensions/UIButton+Kingfisher.swift

@@ -76,9 +76,7 @@ extension KingfisherClass where Base: UIButton {
             options: options,
             progressBlock: { receivedSize, totalSize in
                 guard resource.downloadURL == self.webURL(for: state) else { return }
-                if let progressBlock = progressBlock {
-                    progressBlock(receivedSize, totalSize)
-                }
+                progressBlock?(receivedSize, totalSize)
             },
             completionHandler: { result in
                 DispatchQueue.main.safeAsync {

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

@@ -42,48 +42,54 @@ extension KingfisherClass where Base: WKInterfaceImage {
      - returns: A task represents the retrieving process.
      */
     @discardableResult
-    public func setImage(_ resource: Resource?,
+    public func setImage(with resource: Resource?,
+                         placeholder: Image? = nil,
                          options: KingfisherOptionsInfo? = nil,
                          progressBlock: DownloadProgressBlock? = nil,
-                         completionHandler: CompletionHandler? = nil) -> SessionDataTask? {
+                         completionHandler: ((Result<RetrieveImageResult>) -> Void)? = nil) -> DownloadTask?
+    {
         guard let resource = resource else {
-            setWebURL(nil)
-            completionHandler?(nil, nil, .none, nil)
+            base.setImage(placeholder)
+            webURL = nil
+            completionHandler?(.failure(KingfisherError2.imageSettingError(reason: .emptyResource)))
             return nil
         }
 
-        setWebURL(resource.downloadURL)
+        let options = KingfisherManager.shared.defaultOptions + (options ?? .empty)
+        if !options.keepCurrentImageWhileLoading {
+            base.setImage(placeholder)
+        }
 
+        webURL = resource.downloadURL
         let task = KingfisherManager.shared.retrieveImage(
             with: resource,
-            options: KingfisherManager.shared.defaultOptions + (options ?? .empty),
+            options: options,
             progressBlock: { receivedSize, totalSize in
-                guard resource.downloadURL == self.webURL else {
-                    return
-                }
-
+                guard resource.downloadURL == self.webURL else { return }
                 progressBlock?(receivedSize, totalSize)
             },
-            completionHandler: { [weak base] image, error, cacheType, imageURL in
+            completionHandler: { result in
                 DispatchQueue.main.safeAsync {
-                    guard let strongBase = base, imageURL == self.webURL else {
-                        completionHandler?(image, error, cacheType, imageURL)
+                    guard resource.downloadURL == self.webURL else {
+                        let error = KingfisherError2.imageSettingError(
+                            reason: .notCurrentResource(result: result, resource: resource))
+                        completionHandler?(.failure(error))
                         return
                     }
 
-                    self.setImageTask(nil)
-                    guard let image = image else {
-                        completionHandler?(nil, error, cacheType, imageURL)
-                        return
+                    self.imageTask = nil
+
+                    switch result {
+                    case .success(let value):
+                        self.base.setImage(value.image)
+                        completionHandler?(result)
+                    case .failure:
+                        completionHandler?(result)
                     }
-                
-                    strongBase.setImage(image)
-                    completionHandler?(image, error, cacheType, imageURL)
                 }
             })
 
-        setImageTask(task)
-
+        imageTask = task
         return task
     }
 
@@ -101,21 +107,15 @@ private var lastURLKey: Void?
 private var imageTaskKey: Void?
 
 extension KingfisherClass where Base: WKInterfaceImage {
-    /// Get the image URL binded to this image view.
-    public var webURL: URL? {
-        return objc_getAssociatedObject(base, &lastURLKey) as? URL
-    }
-
-    fileprivate func setWebURL(_ url: URL?) {
-        objc_setAssociatedObject(base, &lastURLKey, url, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-    }
-
-    fileprivate var imageTask: SessionDataTask? {
-        return objc_getAssociatedObject(base, &imageTaskKey) as? SessionDataTask
+    /// Get the image URL bound to this image view.
+    public private(set) var webURL: URL? {
+        get { return getAssociatedObject(base, &lastURLKey) }
+        set { setRetainedAssociatedObject(base, &lastURLKey, newValue) }
     }
 
-    fileprivate func setImageTask(_ task: SessionDataTask?) {
-        objc_setAssociatedObject(base, &imageTaskKey, task, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+    private var imageTask: DownloadTask? {
+        get { return getAssociatedObject(base, &imageTaskKey) }
+        set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)}
     }
 }
 

+ 94 - 8
Sources/General/Deprecated.swift

@@ -32,7 +32,7 @@ import UIKit
 
 extension KingfisherClass where Base: Image {
     @available(*, deprecated, message:
-    "Pass parameters with `ImageCreatingOptions`, use `image(with:options:)` instead.")
+    "Will be removed soon. Pass parameters with `ImageCreatingOptions`, use `image(with:options:)` instead.")
     public static func image(
         data: Data,
         scale: CGFloat,
@@ -48,7 +48,7 @@ extension KingfisherClass where Base: Image {
     }
     
     @available(*, deprecated, message:
-    "Pass parameters with `ImageCreatingOptions`, use `animatedImage(with:options:)` instead.")
+    "Will be removed soon. Pass parameters with `ImageCreatingOptions`, use `animatedImage(with:options:)` instead.")
     public static func animated(
         with data: Data,
         scale: CGFloat = 1.0,
@@ -62,12 +62,15 @@ extension KingfisherClass where Base: Image {
     }
 }
 
-@available(*, deprecated, message: "Use `Result<RetrieveImageResult>` based callback instead")
-public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void)
+@available(*, deprecated, message: "Will be removed soon. Use `Result<RetrieveImageResult>` based callback instead")
+public typealias CompletionHandler =
+    ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void)
 
-@available(*, deprecated, message: "Use `Result<ImageDownloadResult>` based callback instead")
-public typealias ImageDownloaderCompletionHandler = ((_ image: Image?, _ error: NSError?, _ url: URL?, _ originalData: Data?) -> Void)
+@available(*, deprecated, message: "Will be removed soon. Use `Result<ImageDownloadResult>` based callback instead")
+public typealias ImageDownloaderCompletionHandler =
+    ((_ image: Image?, _ error: NSError?, _ url: URL?, _ originalData: Data?) -> Void)
 
+@available(*, deprecated, message: "Will be removed soon. Use `DownloadTask` to cancel a task.")
 extension RetrieveImageTask {
     @available(*, deprecated, message: "RetrieveImageTask.empty will be removed soon. Use `nil` to represnt a no task.")
     public static let empty = RetrieveImageTask()
@@ -121,11 +124,11 @@ extension ImageDownloader {
     }
 }
 
-@available(*, deprecated, message: "RetrieveImageDownloadTask is removed. Use `SessionDataTask` to cancel a task.")
+@available(*, deprecated, message: "RetrieveImageDownloadTask is removed. Use `DownloadTask` to cancel a task.")
 public struct RetrieveImageDownloadTask {
 }
 
-@available(*, deprecated, message: "RetrieveImageTask is removed. Use `SessionDataTask` to cancel a task.")
+@available(*, deprecated, message: "RetrieveImageTask is removed. Use `DownloadTask` to cancel a task.")
 public final class RetrieveImageTask {
 }
 
@@ -153,7 +156,9 @@ extension KingfisherClass where Base: ImageView {
         }
     }
 }
+#endif
 
+#if canImport(UIKit)
 extension KingfisherClass where Base: UIButton {
     @available(*, deprecated, message: "Use `Result` based callback instead.")
     @discardableResult
@@ -209,7 +214,87 @@ extension KingfisherClass where Base: UIButton {
         }
     }
 }
+#endif
+
+#if os(watchOS)
+import WatchKit
+extension KingfisherClass where Base: WKInterfaceImage {
+    @available(*, deprecated, message: "Use `Result` based callback instead.")
+    @discardableResult
+    public func setImage(_ resource: Resource?,
+                         placeholder: Image? = nil,
+                         options: KingfisherOptionsInfo? = nil,
+                         progressBlock: DownloadProgressBlock? = nil,
+                         completionHandler: CompletionHandler?) -> DownloadTask?
+    {
+        return setImage(
+            with: resource,
+            placeholder: placeholder,
+            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)
+            }
+        }
+    }
+}
+#endif
 
+#if os(macOS)
+extension KingfisherClass where Base: NSButton {
+    @discardableResult
+    @available(*, deprecated, message: "Use `Result` based callback instead.")
+    public func setImage(with resource: Resource?,
+                         placeholder: Image? = nil,
+                         options: KingfisherOptionsInfo? = nil,
+                         progressBlock: DownloadProgressBlock? = nil,
+                         completionHandler: CompletionHandler?) -> DownloadTask?
+    {
+        return setImage(
+            with: resource,
+            placeholder: placeholder,
+            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)
+            }
+        }
+    }
+    
+    @discardableResult
+    @available(*, deprecated, message: "Use `Result` based callback instead.")
+    public func setAlternateImage(with resource: Resource?,
+                                  placeholder: Image? = nil,
+                                  options: KingfisherOptionsInfo? = nil,
+                                  progressBlock: DownloadProgressBlock? = nil,
+                                  completionHandler: CompletionHandler?) -> DownloadTask?
+    {
+        return setAlternateImage(
+            with: resource,
+            placeholder: placeholder,
+            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)
+            }
+        }
+    }
+}
 #endif
 
 extension ImageCache {
@@ -309,3 +394,4 @@ extension ImageCache {
         set { diskStorage.config.expiration = .seconds(newValue) }
     }
 }
+

+ 1 - 0
Sources/General/KingfisherError.swift

@@ -42,6 +42,7 @@ public enum KingfisherError2: Error {
         case invalidHTTPStatusCode(response: HTTPURLResponse)
         case URLSessionError(error: Error)
         case dataModifyingFailed(task: SessionDataTask)
+        case noURLResponse
     }
     
     public enum CacheErrorReason {

+ 8 - 0
Sources/Image/Image.swift

@@ -89,6 +89,14 @@ extension KingfisherClass where Base: Image {
         set { setRetainedAssociatedObject(base, &imageSourceKey, newValue) }
     }
     #endif
+    
+    var cost: Int {
+        let pixel = Int(size.width * size.height * scale * scale)
+        guard let cgImage = cgImage else {
+            return pixel * 4 / 1024
+        }
+        return pixel * cgImage.bitsPerPixel / 8 / 1024
+    }
 }
 
 // MARK: - Image Conversion

+ 17 - 4
Sources/Networking/ImageDownloader.swift

@@ -117,9 +117,10 @@ open class ImageDownloader {
         sessionHandler.onValidStatusCode.delegate(on: self) { (self, code) in
             return (self.delegate ?? self).isValidStatusCode(code, for: self)
         }
-        sessionHandler.onDownloadingFinished.delegate(on: self) { (self, result) in
-//            self.delegate?.imageDownloader(
-//                self, didFinishDownloadingImageForURL: result.value?.0, with: result.value?.1, error: result.error)
+        sessionHandler.onDownloadingFinished.delegate(on: self) { (self, value) in
+            let (url, result) = value
+            self.delegate?.imageDownloader(
+                self, didFinishDownloadingImageForURL: url, with: result.value, error: result.error)
         }
         sessionHandler.onDidDownloadData.delegate(on: self) { (self, task) in
             guard let url = task.task.originalRequest?.url else {
@@ -244,7 +245,7 @@ class SessionDelegate: NSObject {
     private let lock = NSLock()
 
     let onValidStatusCode = Delegate<Int, Bool>()
-    let onDownloadingFinished = Delegate<Result<(URL, URLResponse)>, Void>()
+    let onDownloadingFinished = Delegate<(URL, Result<URLResponse>), Void>()
     let onDidDownloadData = Delegate<SessionDataTask, Data?>()
 
     let onReceiveSessionChallenge = Delegate<(
@@ -376,6 +377,18 @@ extension SessionDelegate: URLSessionDataDelegate {
             return
         }
 
+        if let url = task.originalRequest?.url {
+            let result: Result<(URLResponse)>
+            if let error = error {
+                result = .failure(KingfisherError2.responseError(reason: .URLSessionError(error: error)))
+            } else if let response = task.response {
+                result = .success(response)
+            } else {
+                result = .failure(KingfisherError2.responseError(reason: .noURLResponse))
+            }
+            onDownloadingFinished.call((url, result))
+        }
+
         let result: Result<(Data, URLResponse?)>
         if let error = error {
             result = .failure(KingfisherError2.responseError(reason: .URLSessionError(error: error)))

+ 2 - 1
Tests/KingfisherTests/ImageExtensionTests.swift

@@ -72,7 +72,8 @@ class ImageExtensionTests: XCTestCase {
 
 #if os(iOS) || os(tvOS)
     func testScaleForGIFImage() {
-        let image = KingfisherClass.animated(with: testImageGIFData, scale: 2.0, duration: 0.0, preloadAll: false, onlyFirstFrame: false)
+        let options = ImageCreatingOptions(scale: 2.0, duration: 0.0, preloadAll: false, onlyFirstFrame: false)
+        let image = KingfisherClass<Image>.animatedImage(data: testImageGIFData, options: options)
         XCTAssertNotNil(image, "The image should be initiated.")
         XCTAssertEqual(image!.scale, 2.0, "should have correct scale")
     }

+ 1 - 2
Tests/KingfisherTests/KingfisherOptionsInfoTests.swift

@@ -64,9 +64,8 @@ class KingfisherOptionsInfoTests: XCTestCase {
         XCTAssertFalse(options.cacheOriginalImage)
     }
     
-
     func testSetOptionsShouldParseCorrectly() {
-        let cache = try! ImageCache(name: "com.onevcat.Kingfisher.KingfisherOptionsInfoTests")
+        let cache = ImageCache(name: "com.onevcat.Kingfisher.KingfisherOptionsInfoTests")
         let downloader = ImageDownloader(name: "com.onevcat.Kingfisher.KingfisherOptionsInfoTests")
         
 #if os(macOS)

+ 74 - 87
Tests/KingfisherTests/NSButtonExtensionTests.swift

@@ -38,15 +38,17 @@ class NSButtonExtensionTests: XCTestCase {
     }
 
     override class func tearDown() {
-        super.tearDown()
         LSNocilla.sharedInstance().stop()
+        super.tearDown()
     }
 
     override func setUp() {
         super.setUp()
-        // Put setup code here. This method is called before the invocation of each test method in the class.
+        
         button = NSButton()
         KingfisherManager.shared.downloader = ImageDownloader(name: "testDownloader")
+        KingfisherManager.shared.defaultOptions = [.waitForCache]
+        
         cleanDefaultCache()
     }
 
@@ -54,126 +56,111 @@ class NSButtonExtensionTests: XCTestCase {
         // Put teardown code here. This method is called after the invocation of each test method in the class.
         LSNocilla.sharedInstance().clearStubs()
         button = nil
-
         cleanDefaultCache()
-
+        KingfisherManager.shared.defaultOptions = .empty
         super.tearDown()
     }
 
     func testDownloadAndSetImage() {
-        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)!
-
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        stub(url, data: testImageData2, length: 123)
+        
         var progressBlockIsCalled = false
 
-        cleanDefaultCache()
-
-        button.kf.setImage(with: url, placeholder: nil, options: nil, progressBlock: { receivedSize, totalSize in
-            progressBlockIsCalled = true
-        }) { image, error, cacheType, imageURL in
-            expectation.fulfill()
-
-            XCTAssert(progressBlockIsCalled, "progressBlock should be called at least once.")
-            XCTAssert(image != nil, "Downloaded image should exist.")
-            XCTAssert(image! == testImage, "Downloaded image should be the same as test image.")
-            XCTAssert(self.button.image! == testImage, "Downloaded image should be already set to the image for state")
-            XCTAssert(self.button.kf.webURL == imageURL, "Web URL should equal to the downloaded url.")
-            XCTAssert(cacheType == .none, "The cache type should be none here. This image was just downloaded. But now is: \(cacheType)")
+        button.kf.setImage(with: url, progressBlock: { _, _ in progressBlockIsCalled = true }) {
+            result in
+            XCTAssertTrue(progressBlockIsCalled)
+            
+            let image = result.value?.image
+            XCTAssertNotNil(image)
+            XCTAssertTrue(image!.renderEqual(to: testImage))
+            XCTAssertTrue(self.button.image!.renderEqual(to: testImage))
+            XCTAssertEqual(result.value?.imageURL, self.button.kf.webURL)
+            XCTAssertEqual(result.value!.cacheType, .none)
+            
+            exp.fulfill()
         }
-        waitForExpectations(timeout: 5, handler: nil)
+        waitForExpectations(timeout: 1, handler: nil)
     }
 
     func testDownloadAndSetAlternateImage() {
-        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)!
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        stub(url, data: testImageData2, length: 123)
 
         var progressBlockIsCalled = false
-        button.kf.setAlternateImage(with: url, placeholder: nil, options: nil, progressBlock: { receivedSize, totalSize in
-            progressBlockIsCalled = true
-        }) { image, error, cacheType, imageURL in
-            expectation.fulfill()
-
-            XCTAssert(progressBlockIsCalled, "progressBlock should be called at least once.")
-            XCTAssert(image != nil, "Downloaded image should exist.")
-            XCTAssert(image! == testImage, "Downloaded image should be the same as test image.")
-            XCTAssert(self.button.alternateImage! == testImage, "Downloaded image should be already set to the image for state")
-            XCTAssert(self.button.kf.alternateWebURL == imageURL, "Web URL should equal to the downloaded url.")
-            XCTAssert(cacheType == .none, "cacheType should be .None since the image was just downloaded.")
+        button.kf.setAlternateImage(with: url, progressBlock: { _, _ in progressBlockIsCalled = true }) {
+            result in
+            XCTAssertTrue(progressBlockIsCalled)
+            
+            let image = result.value?.image
+            XCTAssertNotNil(image)
+            XCTAssertTrue(image!.renderEqual(to: testImage))
+            XCTAssertTrue(self.button.alternateImage!.renderEqual(to: testImage))
+            XCTAssertEqual(result.value?.imageURL, self.button.kf.alternateWebURL)
+            XCTAssertEqual(result.value!.cacheType, .none)
+            
+            exp.fulfill()
 
         }
         waitForExpectations(timeout: 5, handler: nil)
     }
 
     func testCacnelImageTask() {
-        let expectation = self.expectation(description: "wait for downloading image")
-
-        let URLString = testKeys[0]
-        let stub = stubRequest("GET", URLString).andReturn(200)?.withBody(testImageData)?.delay()
-        let url = URL(string: URLString)!
-
-        button.kf.setImage(with: url, placeholder: nil, options: nil, progressBlock: { receivedSize, totalSize in
-            
-        }) { image, error, cacheType, imageURL in
-            XCTAssertNotNil(error)
-            XCTAssertEqual(error?.code, NSURLErrorCancelled)
-
-            expectation.fulfill()
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        let stub = delayedStub(url, data: testImageData2)
+        
+        button.kf.setImage(with: url) { result in
+            XCTAssertNotNil(result.error)
+            XCTAssertTrue((result.error as! KingfisherError2).isTaskCancelled)
         }
         
-        delay(0.1) { 
-            self.button.kf.cancelImageDownloadTask()
-            _ = stub!.go()
+        self.button.kf.cancelImageDownloadTask()
+        _ = stub.go()
+        delay(0.1) {
+            exp.fulfill()
         }
 
-        waitForExpectations(timeout: 5, handler: nil)
+        waitForExpectations(timeout: 1, handler: nil)
     }
 
     func testCacnelAlternateImageTask() {
-        let expectation = self.expectation(description: "wait for downloading image")
-
-        let URLString = testKeys[0]
-        let stub = stubRequest("GET", URLString).andReturn(200)?.withBody(testImageData)?.delay()
-        let url = URL(string: URLString)!
-
-        _ = button.kf.setAlternateImage(with: url, placeholder: nil, options: nil, progressBlock: { receivedSize, totalSize in
-            XCTFail("Progress block should not be called.")
-        }) { image, error, cacheType, imageURL in
-            XCTAssertNotNil(error)
-            XCTAssertEqual(error?.code, NSURLErrorCancelled)
-
-            expectation.fulfill()
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        let stub = delayedStub(url, data: testImageData2)
+        
+        button.kf.setAlternateImage(with: url) { result in
+            XCTAssertNotNil(result.error)
+            XCTAssertTrue((result.error as! KingfisherError2).isTaskCancelled)
         }
-
-        delay(0.1) { 
-            self.button.kf.cancelAlternateImageDownloadTask()
-            _ = stub!.go()
+        
+        self.button.kf.cancelAlternateImageDownloadTask()
+        _ = stub.go()
+        delay(0.1) {
+            exp.fulfill()
         }
-
-        waitForExpectations(timeout: 5, handler: nil)
+        
+        waitForExpectations(timeout: 1, handler: nil)
     }
     
     func testSettingNilURL() {
-        let expectation = self.expectation(description: "wait for downloading image")
-        
+        let exp = expectation(description: #function)
         let url: URL? = nil
-        button.kf.setAlternateImage(with: url, placeholder: nil, options: nil, progressBlock: { receivedSize, totalSize in
-            XCTFail("Progress block should not be called.")
-        }) { image, error, cacheType, imageURL in
-            XCTAssertNil(image)
-            XCTAssertNil(error)
-            XCTAssertEqual(cacheType, CacheType.none)
-            XCTAssertNil(imageURL)
+        button.kf.setAlternateImage(with: url, progressBlock: { _, _ in XCTFail() }) {
+            result in
+            XCTAssertNil(result.value)
+            XCTAssertNotNil(result.error)
             
-            expectation.fulfill()
+            guard case KingfisherError2.imageSettingError(reason: .emptyResource) = result.error! else {
+                XCTFail()
+                fatalError()
+            }
+            exp.fulfill()
         }
         
-        waitForExpectations(timeout: 5, handler: nil)
+        waitForExpectations(timeout: 1, handler: nil)
     }
 
 }