Просмотр исходного кода

Merge pull request #42 from onevcat/1.3.0

[WIP] 1.3.0
Wei Wang 10 лет назад
Родитель
Сommit
342c32aeea

+ 1 - 1
Kingfisher-Demo/ViewController.swift

@@ -59,7 +59,7 @@ extension ViewController: UICollectionViewDataSource {
         let cell = collectionView.dequeueReusableCellWithReuseIdentifier("collectionViewCell", forIndexPath: indexPath) as! CollectionViewCell
         cell.cellImageView.kf_setImageWithURL(NSURL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.row + 1).jpg")!, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             println("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
-        }) { (image, error, imageURL) -> () in
+        }) { (image, error, cacheType, imageURL) -> () in
             println("\(indexPath.row + 1): Finished")
         }
         

+ 1 - 1
Kingfisher.podspec

@@ -1,7 +1,7 @@
 Pod::Spec.new do |s|
 
   s.name         = "Kingfisher"
-  s.version      = "1.2.0"
+  s.version      = "1.3.0"
   s.summary      = "A lightweight and pure Swift implemented library for downloading and cacheing image from the web."
 
   s.description  = <<-DESC

+ 41 - 1
Kingfisher/ImageCache.swift

@@ -26,6 +26,20 @@
 
 import Foundation
 
+/**
+This notification will be sent when the disk cache got cleaned either there are cached files expired or the total size exceeding the max allowed size. The `clearDiskCache` method will not trigger this notification.
+
+The `object` of this notification is the `ImageCache` object which sends the notification.
+
+A list of removed hashes (files) could be retrieved by accessing the array under `KingfisherDiskCacheCleanedHashKey` key in `userInfo` of the notification object you received. By checking the array, you could know the hash codes of files are removed.
+*/
+public let KingfisherDidCleanDiskCacheNotification = "com.onevcat.Kingfisher.KingfisherDidCleanDiskCacheNotification"
+
+/**
+Key for array of cleaned hashes in `userInfo` of `KingfisherDidCleanDiskCacheNotification`.
+*/
+public let KingfisherDiskCacheCleanedHashKey = "com.onevcat.Kingfisher.cleanedHash"
+
 private let defaultCacheName = "default"
 private let cacheReverseDNS = "com.onevcat.Kingfisher.ImageCache."
 private let ioQueueName = "com.onevcat.Kingfisher.ImageCache.ioQueue."
@@ -43,9 +57,12 @@ Cache type of a cached image.
 - Disk:   The image is cached in disk.
 */
 public enum CacheType {
-    case Memory, Disk
+    case Memory, Disk, None
 }
 
+/**
+*	`ImageCache` represents both the memory and disk cache system of Kingfisher. While a default image cache object will be used if you prefer the extension methods of Kingfisher, you can create your own cache object and configure it as your need. You should use an `ImageCache` object to manipulate memory and disk cache for Kingfisher.
+*/
 public class ImageCache {
 
     //Memory
@@ -412,6 +429,9 @@ extension ImageCache {
                     
                     for fileURL in sortedFiles {
                         if (self.fileManager.removeItemAtURL(fileURL, error: nil)) {
+                            
+                            URLsToDelete.append(fileURL)
+                            
                             if let fileSize = cachedFiles[fileURL]?[NSURLTotalFileAllocatedSizeKey] as? NSNumber {
                                 diskCacheSize -= fileSize.unsignedLongValue
                             }
@@ -424,6 +444,15 @@ extension ImageCache {
                 }
                 
                 dispatch_async(dispatch_get_main_queue(), { () -> Void in
+                    
+                    if URLsToDelete.count != 0 {
+                        let cleanedHashes = URLsToDelete.map({ (url) -> String in
+                            return url.lastPathComponent!
+                        })
+                        
+                        NSNotificationCenter.defaultCenter().postNotificationName(KingfisherDidCleanDiskCacheNotification, object: self, userInfo: [KingfisherDiskCacheCleanedHashKey: cleanedHashes])
+                    }
+                    
                     if let completionHandler = completionHandler {
                         completionHandler()
                     }
@@ -496,6 +525,17 @@ public extension ImageCache {
         return CacheCheckResult(cached: false, cacheType: nil)
     }
     
+    /**
+    Get the hash for the key. This could be used for matching files.
+    
+    :param: key The key which is used for caching.
+    
+    :returns: Corresponding hash.
+    */
+    public func hashForKey(key: String) -> String {
+        return cacheFileNameForKey(key)
+    }
+    
     /**
     Calculate the disk size taken by cache. 
     It is the total allocated size of the cached files in bytes.

+ 48 - 2
Kingfisher/ImageDownloader.swift

@@ -26,9 +26,13 @@
 
 import Foundation
 
+/// Progress update block of downloader.
 public typealias ImageDownloaderProgressBlock = DownloadProgressBlock
-public typealias ImageDownloaderCompletionHandler = CompletionHandler
 
+/// Completion block of downloader.
+public typealias ImageDownloaderCompletionHandler = ((image: UIImage?, error: NSError?, imageURL: NSURL?) -> ())
+
+/// Download task.
 public typealias RetrieveImageDownloadTask = NSURLSessionDataTask
 
 private let defaultDownloaderName = "default"
@@ -36,6 +40,24 @@ private let downloaderBarrierName = "com.onevcat.Kingfisher.ImageDownloader.Barr
 private let imageProcessQueueName = "com.onevcat.Kingfisher.ImageDownloader.Process."
 private let instance = ImageDownloader(name: defaultDownloaderName)
 
+/**
+*	Protocol of `ImageDownloader`.
+*/
+@objc public protocol ImageDownloaderDelegate {
+    /**
+    Called when the `ImageDownloader` object successfully downloaded an image from specified URL.
+    
+    :param: downloader The `ImageDownloader` object finishes the downloading.
+    :param: image      Downloaded image.
+    :param: URL        URL of the original request URL.
+    :param: response   The response object of the downloading process.
+    */
+    optional func imageDownloader(downloader: ImageDownloader, didDownloadImage image: UIImage, forURL URL: NSURL, withResponse response: NSURLResponse)
+}
+
+/**
+*	`ImageDownloader` represents a downloading manager for requesting the image with a URL from server.
+*/
 public class ImageDownloader: NSObject {
     
     class ImageFetchLoad {
@@ -55,6 +77,9 @@ public class ImageDownloader: NSObject {
     /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this set will be ignored. You can use this set to specify the self-signed site.
     public var trustedHosts: Set<String>?
     
+    /// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more.
+    public weak var delegate: ImageDownloaderDelegate?
+    
     // MARK: - Internal property
     let barrierQueue: dispatch_queue_t
     let processQueue: dispatch_queue_t
@@ -187,7 +212,9 @@ public extension ImageDownloader {
 
 // MARK: - NSURLSessionTaskDelegate
 extension ImageDownloader: NSURLSessionDataDelegate {
-    
+    /**
+    This method is exposed since the compiler requests. Do not call it.
+    */
     public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
         
         if let URL = dataTask.originalRequest.URL, callbackPairs = fetchLoads[URL]?.callbacks {
@@ -198,6 +225,9 @@ extension ImageDownloader: NSURLSessionDataDelegate {
         completionHandler(NSURLSessionResponseDisposition.Allow)
     }
     
+    /**
+    This method is exposed since the compiler requests. Do not call it.
+    */
     public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
 
         if let URL = dataTask.originalRequest.URL, fetchLoad = fetchLoads[URL] {
@@ -216,6 +246,9 @@ extension ImageDownloader: NSURLSessionDataDelegate {
         }
     }
     
+    /**
+    This method is exposed since the compiler requests. Do not call it.
+    */
     public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
         
         if let URL = task.originalRequest.URL {
@@ -228,6 +261,9 @@ extension ImageDownloader: NSURLSessionDataDelegate {
                     
                     if let fetchLoad = self.fetchLoads[URL] {
                         if let image = UIImage(data: fetchLoad.responseData) {
+                            
+                            self.delegate?.imageDownloader?(self, didDownloadImage: image, forURL: URL, withResponse: task.response!)
+                            
                             if fetchLoad.shouldDecode {
                                 self.callbackWithImage(image.kf_decodedImage(), error: nil, imageURL: URL)
                             } else {
@@ -235,6 +271,13 @@ extension ImageDownloader: NSURLSessionDataDelegate {
                             }
                             
                         } else {
+                            // If server response is 304 (Not Modified), inform the callback handler with NotModified error.
+                            // It should be handled to get an image from cache, which is response of a manager object.
+                            if let res = task.response as? NSHTTPURLResponse where res.statusCode == 304 {
+                                self.callbackWithImage(nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.NotModified.rawValue, userInfo: nil), imageURL: URL)
+                                return
+                            }
+                            
                             self.callbackWithImage(nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.BadData.rawValue, userInfo: nil), imageURL: URL)
                         }
                     } else {
@@ -247,6 +290,9 @@ extension ImageDownloader: NSURLSessionDataDelegate {
         }
     }
 
+    /**
+    This method is exposed since the compiler requests. Do not call it.
+    */
     public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void) {
 
         if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {

+ 20 - 3
Kingfisher/KingfisherManager.swift

@@ -59,6 +59,7 @@ The error code.
 */
 public enum KingfisherError: Int {
     case BadData = 10000
+    case NotModified = 10001
     case InvalidURL = 20000
 }
 
@@ -69,12 +70,17 @@ private let instance = KingfisherManager()
 */
 public class KingfisherManager {
 
+    /**
+    *	Options to control some downloader and cache behaviors.
+    */
     public typealias Options = (forceRefresh: Bool, lowPriority: Bool, cacheMemoryOnly: Bool, shouldDecode: Bool)
     
+    /// A preset option tuple with all value set to `false`.
     public static var OptionsNone: Options = {
         return (forceRefresh: false, lowPriority: false, cacheMemoryOnly: false, shouldDecode: false)
     }()
     
+    /// Shared manager used by the extensions across Kingfisher.
     public class var sharedManager: KingfisherManager {
         return instance
     }
@@ -153,7 +159,7 @@ public class KingfisherManager {
             } else {
                 let diskTask = targetCache.retrieveImageForKey(key, options: options, completionHandler: { (image, cacheType) -> () in
                     if image != nil {
-                        completionHandler?(image: image, error: nil, imageURL: URL)
+                        completionHandler?(image: image, error: nil, cacheType:cacheType, imageURL: URL)
                     } else {
                         self.downloadAndCacheImageWithURL(URL,
                             forKey: key,
@@ -185,7 +191,18 @@ public class KingfisherManager {
             progressBlock?(receivedSize: receivedSize, totalSize: totalSize)
             return
         }) { (image, error, imageURL) -> () in
-            completionHandler?(image: image, error: error, imageURL: URL)
+
+            if let error = error where error.code == KingfisherError.NotModified.rawValue {
+                // Not modified. Try to find the image from cache. 
+                // (The image should be in cache. It should be ensured by the framework users.)
+                targetCache.retrieveImageForKey(key, options: options, completionHandler: { (cacheImage, cacheType) -> () in
+                    completionHandler?(image: cacheImage, error: nil, cacheType: cacheType, imageURL: URL)
+
+                })
+                return
+            }
+            
+            completionHandler?(image: image, error: error, cacheType: .None, imageURL: URL)
             if let image = image {
                 targetCache.storeImage(image, forKey: key, toDisk: !options.cacheMemoryOnly, completionHandler: nil)
             }
@@ -203,4 +220,4 @@ public extension KingfisherManager {
     {
         return retrieveImageWithURL(URL, optionsInfo: [.Options : options], progressBlock: progressBlock, completionHandler: completionHandler)
     }
-}
+}

+ 23 - 2
Kingfisher/KingfisherOptions.swift

@@ -33,19 +33,40 @@ public struct KingfisherOptions : RawOptionSetType {
     typealias RawValue = UInt
     private var value: UInt = 0
     init(_ value: UInt) { self.value = value }
+    
+    /**
+    Init an option
+    
+    :param: value Raw value of the option.
+    
+    :returns: An option represets input value.
+    */
     public init(rawValue value: UInt) { self.value = value }
+    
+    /**
+    Init a None option
+    
+    :param: nilLiteral Void.
+    
+    :returns: An option represents None.
+    */
     public init(nilLiteral: ()) { self.value = 0 }
+    
+    /// An option represents None.
     public static var allZeros: KingfisherOptions { return self(0) }
-    static func fromMask(raw: UInt) -> KingfisherOptions { return self(raw) }
+    
+    /// Raw value of the option.
     public var rawValue: UInt { return self.value }
     
+    static func fromMask(raw: UInt) -> KingfisherOptions { return self(raw) }
+
     /// None options. Kingfisher will keep its default behavior.
     public static var None: KingfisherOptions { return self(0) }
     
     /// Download in a low priority.
     public static var LowPriority: KingfisherOptions { return KingfisherOptions(1 << 0) }
     
-    /// Ignore cache. Always download the image and cache it again.
+    /// Try to send request to server first. If response code is 304 (Not Modified), use the cached image. Otherwise, download the image and cache it again.
     public static var ForceRefresh: KingfisherOptions { return KingfisherOptions(1 << 1) }
     
     /// Only cache downloaded image to memory, not cache in disk.

+ 4 - 4
Kingfisher/UIButton+Kingfisher.swift

@@ -127,12 +127,12 @@ public extension UIButton {
                     progressBlock(receivedSize: receivedSize, totalSize: totalSize)
                 })
             }
-        }) { (image, error, imageURL) -> () in
+        }) { (image, error, cacheType, imageURL) -> () in
             dispatch_async(dispatch_get_main_queue(), { () -> Void in
                 if (imageURL == self.kf_webURLForState(state) && image != nil) {
                     self.setImage(image, forState: state)
                 }
-                completionHandler?(image: image, error: error, imageURL: imageURL)
+                completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
             })
         }
         
@@ -275,12 +275,12 @@ public extension UIButton {
                     progressBlock(receivedSize: receivedSize, totalSize: totalSize)
                 })
             }
-            }) { (image, error, imageURL) -> () in
+            }) { (image, error, cacheType, imageURL) -> () in
                 dispatch_async(dispatch_get_main_queue(), { () -> Void in
                     if (imageURL == self.kf_backgroundWebURLForState(state) && image != nil) {
                         self.setBackgroundImage(image, forState: state)
                     }
-                    completionHandler?(image: image, error: error, imageURL: imageURL)
+                    completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
                 })
         }
         

+ 3 - 3
Kingfisher/UIImageView+Kingfisher.swift

@@ -27,7 +27,7 @@
 import Foundation
 
 public typealias DownloadProgressBlock = ((receivedSize: Int64, totalSize: Int64) -> ())
-public typealias CompletionHandler = ((image: UIImage?, error: NSError?, imageURL: NSURL?) -> ())
+public typealias CompletionHandler = ((image: UIImage?, error: NSError?, cacheType:CacheType, imageURL: NSURL?) -> ())
 
 // MARK: - Set Images
 /**
@@ -122,12 +122,12 @@ public extension UIImageView {
                     progressBlock(receivedSize: receivedSize, totalSize: totalSize)
                 })
             }
-        }) { (image, error, imageURL) -> () in
+        }) { (image, error, cacheType, imageURL) -> () in
             dispatch_async(dispatch_get_main_queue(), { () -> Void in
                 if (imageURL == self.kf_webURL && image != nil) {
                     self.image = image;
                 }
-                completionHandler?(image: image, error: error, imageURL: imageURL)
+                completionHandler?(image: image, error: error, cacheType:cacheType, imageURL: imageURL)
             })
         }
         

+ 27 - 2
KingfisherTests/ImageCacheTests.swift

@@ -34,6 +34,7 @@ private let cacheName = "com.onevcat.Kingfisher.ImageCache.test"
 class ImageCacheTests: XCTestCase {
 
     var cache: ImageCache!
+    var observer: NSObjectProtocol!
     
     override func setUp() {
         super.setUp()
@@ -46,6 +47,7 @@ class ImageCacheTests: XCTestCase {
         super.tearDown()
         cache.clearDiskCache()
         cache = nil
+        observer = nil
     }
     
     func testMaxCachePeriodInSecond() {
@@ -164,12 +166,35 @@ class ImageCacheTests: XCTestCase {
                     self.cache.retrieveImageInDiskCacheForKey(testKeys[0])
                 }
             })
-        
             expectation.fulfill()
+        }
+        
+        self.waitForExpectationsWithTimeout(15, handler: nil)
+    }
+    
+    func testCleanDiskCacheNotification() {
+        let expectation = expectationWithDescription("wait for retriving image")
+        
+        cache.storeImage(testImage, forKey: testKeys[0], toDisk: true) { () -> () in
+
+            self.observer = NSNotificationCenter.defaultCenter().addObserverForName(KingfisherDidCleanDiskCacheNotification, object: self.cache, queue: NSOperationQueue.mainQueue(), usingBlock: { (noti) -> Void in
+
+                XCTAssert(noti.object === self.cache, "The object of notification should be the cache object.")
+                
+                let hashes = noti.userInfo?[KingfisherDiskCacheCleanedHashKey] as! [String]
+                
+                XCTAssertEqual(1, hashes.count, "There should be one and only one file cleaned")
+                XCTAssertEqual(hashes.first!, self.cache.hashForKey(testKeys[0]), "The cleaned file should be the stored one.")
+                
+                NSNotificationCenter.defaultCenter().removeObserver(self.observer)
+                expectation.fulfill()
+            })
             
+            self.cache.maxCachePeriodInSecond = 0
+            self.cache.cleanExpiredDiskCache()
         }
         
-        self.waitForExpectationsWithTimeout(5, handler: nil)
+        waitForExpectationsWithTimeout(1, handler: nil)
     }
 
     // MARK: - Helper

+ 17 - 1
KingfisherTests/ImageDownloaderTests.swift

@@ -144,6 +144,22 @@ class ImageDownloaderTests: XCTestCase {
         waitForExpectationsWithTimeout(1, handler: nil)
     }
     
+    func testServerNotModifiedResponse() {
+        let expectation = expectationWithDescription("wait for server response 304")
+        
+        let URLString = testKeys[0]
+        stubRequest("GET", URLString).andReturn(304)
+        
+        downloader.downloadImageWithURL(NSURL(string: URLString)!, options: KingfisherManager.OptionsNone, progressBlock: { (receivedSize, totalSize) -> () in
+            
+        }) { (image, error, imageURL) -> () in
+            XCTAssertNotNil(error, "There should be an error since server returning 304 and no image downloaded.")
+            XCTAssertEqual(error!.code, KingfisherError.NotModified.rawValue, "The error should be NotModified.")
+            expectation.fulfill()
+        }
+        waitForExpectationsWithTimeout(1, handler: nil)
+    }
+    
     // Since we could not receive one challage, no test for trusted hosts currently.
     // See http://stackoverflow.com/questions/27065372/why-is-a-https-nsurlsession-connection-only-challenged-once-per-domain for more.
     func testSSLCertificateValidation() {
@@ -155,7 +171,7 @@ class ImageDownloaderTests: XCTestCase {
         
         downloader.downloadImageWithURL(URL, progressBlock: nil, completionHandler: { (image, error, imageURL) -> () in
             XCTAssertNotNil(error, "Error should not be nil")
-            XCTAssert(error?.code == NSURLErrorServerCertificateUntrusted, "Error should be NSURLErrorServerCertificateUntrusted")
+            XCTAssert(error?.code == NSURLErrorServerCertificateUntrusted, "Error should be NSURLErrorServerCertificateUntrusted, but \(error)")
             expectation.fulfill()
             LSNocilla.sharedInstance().start()
         })

+ 6 - 2
KingfisherTests/UIButtonExtensionTests.swift

@@ -53,6 +53,7 @@ class UIButtonExtensionTests: 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()
         
         super.tearDown()
@@ -68,7 +69,7 @@ class UIButtonExtensionTests: XCTestCase {
         var progressBlockIsCalled = false
         button.kf_setImageWithURL(URL, forState: UIControlState.Highlighted, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             progressBlockIsCalled = true
-        }) { (image, error, imageURL) -> () in
+        }) { (image, error, cacheType, imageURL) -> () in
             expectation.fulfill()
             
             XCTAssert(progressBlockIsCalled, "progressBlock should be called at least once.")
@@ -76,6 +77,7 @@ class UIButtonExtensionTests: XCTestCase {
             XCTAssert(image! == testImage, "Downloaded image should be the same as test image.")
             XCTAssert(self.button.imageForState(UIControlState.Highlighted)! == testImage, "Downloaded image should be already set to the image for state")
             XCTAssert(self.button.kf_webURLForState(UIControlState.Highlighted) == imageURL, "Web URL should equal to the downloaded url.")
+            XCTAssert(cacheType == .None, "cacheType should be .None since the image was just downloaded.")
         }
         waitForExpectationsWithTimeout(1, handler: nil)
     }
@@ -90,7 +92,7 @@ class UIButtonExtensionTests: XCTestCase {
         var progressBlockIsCalled = false
         button.kf_setBackgroundImageWithURL(URL, forState: UIControlState.Normal, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             progressBlockIsCalled = true
-            }) { (image, error, imageURL) -> () in
+            }) { (image, error, cacheType, imageURL) -> () in
                 expectation.fulfill()
                 
                 XCTAssert(progressBlockIsCalled, "progressBlock should be called at least once.")
@@ -98,6 +100,8 @@ class UIButtonExtensionTests: XCTestCase {
                 XCTAssert(image! == testImage, "Downloaded image should be the same as test image.")
                 XCTAssert(self.button.backgroundImageForState(UIControlState.Normal)! == testImage, "Downloaded image should be already set to the image for state")
                 XCTAssert(self.button.kf_backgroundWebURLForState(UIControlState.Normal) == imageURL, "Web URL should equal to the downloaded url.")
+                XCTAssert(cacheType == .None, "cacheType should be .None since the image was just downloaded.")
+
         }
         waitForExpectationsWithTimeout(1, handler: nil)
     }

+ 9 - 7
KingfisherTests/UIImageViewExtensionTests.swift

@@ -70,7 +70,7 @@ class UIImageViewExtensionTests: XCTestCase {
         
         imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             progressBlockIsCalled = true
-        }) { (image, error, imageURL) -> () in
+        }) { (image, error, cacheType, imageURL) -> () in
             expectation.fulfill()
             
             XCTAssert(progressBlockIsCalled, "progressBlock should be called at least once.")
@@ -78,6 +78,8 @@ class UIImageViewExtensionTests: XCTestCase {
             XCTAssert(image! == testImage, "Downloaded image should be the same as test image.")
             XCTAssert(self.imageView.image! == testImage, "Downloaded image should be already set to the image property.")
             XCTAssert(self.imageView.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.")
         }
         
         waitForExpectationsWithTimeout(1, handler: nil)
@@ -95,7 +97,7 @@ class UIImageViewExtensionTests: XCTestCase {
         
         let task = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             progressBlockIsCalled = true
-        }) { (image, error, imageURL) -> () in
+        }) { (image, error, cacheType, imageURL) -> () in
             completionBlockIsCalled = true
         }
         task.cancel()
@@ -122,19 +124,19 @@ class UIImageViewExtensionTests: XCTestCase {
         
         let task1 = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
 
-            }) { (image, error, imageURL) -> () in
+            }) { (image, error, cacheType, imageURL) -> () in
                 task1Completion = true
         }
         
         let task2 = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             
-            }) { (image, error, imageURL) -> () in
+            }) { (image, error, cacheType, imageURL) -> () in
                 task2Completion = true
         }
         
         let task3 = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             
-            }) { (image, error, imageURL) -> () in
+            }) { (image, error, cacheType, imageURL) -> () in
                 task3Completion = true
         }
         
@@ -163,7 +165,7 @@ class UIImageViewExtensionTests: XCTestCase {
         
         imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: [.TargetCache: cache1], progressBlock: { (receivedSize, totalSize) -> () in
             
-        }) { (image, error, imageURL) -> () in
+        }) { (image, error, cacheType, imageURL) -> () in
             
             XCTAssertTrue(cache1.isImageCachedForKey(URLString).cached, "This image should be cached in cache1.")
             XCTAssertFalse(cache2.isImageCachedForKey(URLString).cached, "This image should not be cached in cache2.")
@@ -171,7 +173,7 @@ class UIImageViewExtensionTests: XCTestCase {
             
             self.imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: [.TargetCache: cache2], progressBlock: { (receivedSize, totalSize) -> () in
                 
-            }, completionHandler: { (image, error, imageURL) -> () in
+            }, completionHandler: { (image, error, cacheType, imageURL) -> () in
                 XCTAssertTrue(cache1.isImageCachedForKey(URLString).cached, "This image should be cached in cache1.")
                 XCTAssertTrue(cache2.isImageCachedForKey(URLString).cached, "This image should be cached in cache2.")
                 XCTAssertFalse(KingfisherManager.sharedManager.cache.isImageCachedForKey(URLString).cached, "This image should not be cached in default cache.")