Jelajahi Sumber

Merge branch 'dopcn-master' of github.com:onevcat/Kingfisher

onevcat 10 tahun lalu
induk
melakukan
fd6bd018cf

+ 10 - 8
Sources/ImageCache.swift

@@ -276,45 +276,47 @@ extension ImageCache {
         }
         
         var block: RetrieveImageDiskTask?
+        let options = options ?? KingfisherEmptyOptionsInfo
+        
         if let image = self.retrieveImageInMemoryCacheForKey(key) {
-            
             //Found image in memory cache.
-            if let options = options where options.backgroundDecode {
+            if options.backgroundDecode {
                 dispatch_async(self.processQueue, { () -> Void in
                     let result = image.kf_decodedImage(scale: options.scaleFactor)
-                    dispatch_async(options.callbackDispatchQueue, { () -> Void in
+                    dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
                         completionHandler(result, .Memory)
                     })
                 })
             } else {
-                completionHandler(image, .Memory)
+                dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
+                    completionHandler(image, .Memory)
+                })
             }
         } else {
             var sSelf: ImageCache! = self
             block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
                 // Begin to load image from disk
-                let options = options ?? KingfisherEmptyOptionsInfo
                 if let image = sSelf.retrieveImageInDiskCacheForKey(key, scale: options.scaleFactor) {
                     if options.backgroundDecode {
                         dispatch_async(sSelf.processQueue, { () -> Void in
                             let result = image.kf_decodedImage(scale: options.scaleFactor)
                             sSelf.storeImage(result!, forKey: key, toDisk: false, completionHandler: nil)
 
-                            dispatch_async(options.callbackDispatchQueue, { () -> Void in
+                            dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
                                 completionHandler(result, .Memory)
                                 sSelf = nil
                             })
                         })
                     } else {
                         sSelf.storeImage(image, forKey: key, toDisk: false, completionHandler: nil)
-                        dispatch_async(options.callbackDispatchQueue, { () -> Void in
+                        dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
                             completionHandler(image, .Disk)
                             sSelf = nil
                         })
                     }
                 } else {
                     // No image found from either memory or disk
-                    dispatch_async(options.callbackDispatchQueue, { () -> Void in
+                    dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
                         completionHandler(nil, nil)
                         sSelf = nil
                     })

+ 7 - 2
Sources/ImageDownloader.swift

@@ -318,7 +318,9 @@ extension ImageDownloader: NSURLSessionDataDelegate {
             fetchLoad.responseData.appendData(data)
             
             for callbackPair in fetchLoad.callbacks {
-                callbackPair.progressBlock?(receivedSize: Int64(fetchLoad.responseData.length), totalSize: dataTask.response!.expectedContentLength)
+                dispatch_async(dispatch_get_main_queue(), { () -> Void in
+                    callbackPair.progressBlock?(receivedSize: Int64(fetchLoad.responseData.length), totalSize: dataTask.response!.expectedContentLength)
+                })
             }
         }
     }
@@ -355,11 +357,14 @@ extension ImageDownloader: NSURLSessionDataDelegate {
     
     private func callbackWithImage(image: Image?, error: NSError?, imageURL: NSURL, originalData: NSData?) {
         if let callbackPairs = fetchLoadForKey(imageURL)?.callbacks {
+            let options = fetchLoadForKey(imageURL)?.options ?? KingfisherEmptyOptionsInfo
             
             self.cleanForURL(imageURL)
             
             for callbackPair in callbackPairs {
-                callbackPair.completionHander?(image: image, error: error, imageURL: imageURL, originalData: originalData)
+                dispatch_async_safely_to_queue(options.callbackDispatchQueue, { () -> Void in
+                    callbackPair.completionHander?(image: image, error: error, imageURL: imageURL, originalData: originalData)
+                })
             }
         }
     }

+ 30 - 23
Sources/ImageView+Kingfisher.swift

@@ -140,6 +140,9 @@ extension ImageView {
     - parameter completionHandler: Called when the image retrieved and set.
     
     - returns: A task represents the retrieving process.
+     
+    - note: `completionHandler` will be invoked in main thread.
+     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
     */
     public func kf_setImageWithResource(resource: Resource,
                                 placeholderImage: Image?,
@@ -158,6 +161,9 @@ extension ImageView {
     - parameter completionHandler: Called when the image retrieved and set.
     
     - returns: A task represents the retrieving process.
+     
+    - note: `completionHandler` will be invoked in main thread.
+     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
     */
     public func kf_setImageWithURL(URL: NSURL,
                       placeholderImage: Image?,
@@ -177,6 +183,9 @@ extension ImageView {
     - 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.
     */
     public func kf_setImageWithResource(resource: Resource,
                                 placeholderImage: Image?,
@@ -199,16 +208,12 @@ extension ImageView {
         let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo,
             progressBlock: { receivedSize, totalSize in
                 if let progressBlock = progressBlock {
-                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
-                        progressBlock(receivedSize: receivedSize, totalSize: totalSize)
-                        
-                    })
+                    progressBlock(receivedSize: receivedSize, totalSize: totalSize)
                 }
             },
             completionHandler: {[weak self] image, error, cacheType, imageURL in
                 
-                dispatch_async_safely_main_queue {
-                    
+                dispatch_async_safely_to_main_queue {
                     guard let sSelf = self where imageURL == sSelf.kf_webURL else {
                         completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
                         return
@@ -221,27 +226,26 @@ extension ImageView {
                         completionHandler?(image: nil, error: error, cacheType: cacheType, imageURL: imageURL)
                         return
                     }
-
-
+                    
                     if let transitionItem = optionsInfo?.kf_firstMatchIgnoringAssociatedValue(.Transition(.None)),
                         case .Transition(let transition) = transitionItem where cacheType == .None {
-#if !os(OSX)
-                            UIView.transitionWithView(sSelf, duration: 0.0, options: [],
-                                animations: {
-                                    indicator?.kf_stopAnimating()
-                                },
-                                completion: { finished in
-                                    UIView.transitionWithView(sSelf, duration: transition.duration,
-                                        options: transition.animationOptions,
-                                        animations: {
-                                            transition.animations?(sSelf, image)
-                                        },
-                                        completion: { finished in
-                                            transition.completion?(finished)
-                                            completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
+                            #if !os(OSX)
+                                UIView.transitionWithView(sSelf, duration: 0.0, options: [],
+                                    animations: {
+                                        indicator?.kf_stopAnimating()
+                                    },
+                                    completion: { finished in
+                                        UIView.transitionWithView(sSelf, duration: transition.duration,
+                                            options: transition.animationOptions,
+                                            animations: {
+                                                transition.animations?(sSelf, image)
+                                            },
+                                            completion: { finished in
+                                                transition.completion?(finished)
+                                                completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
                                         })
                                 })
-#endif
+                            #endif
                     } else {
                         indicator?.kf_stopAnimating()
                         sSelf.image = image
@@ -265,6 +269,9 @@ extension ImageView {
     - 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.
     */
     
     public func kf_setImageWithURL(URL: NSURL,

+ 10 - 3
Sources/ThreadHelper.swift

@@ -26,11 +26,18 @@
 
 import Foundation
 
-func dispatch_async_safely_main_queue(block: ()->()) {
-    if NSThread.isMainThread() {
+func dispatch_async_safely_to_main_queue(block: ()->()) {
+    dispatch_async_safely_to_queue(dispatch_get_main_queue(), block)
+}
+
+// This methd will dispatch the `block` to a specified `queue`.
+// If the `queue` is the main queue, and current thread is main thread, the block 
+// will be invoked immediately instead of being dispatched.
+func dispatch_async_safely_to_queue(queue: dispatch_queue_t, _ block: ()->()) {
+    if queue === dispatch_get_main_queue() && NSThread.isMainThread() {
         block()
     } else {
-        dispatch_async(dispatch_get_main_queue()) {
+        dispatch_async(queue) {
             block()
         }
     }

+ 28 - 10
Sources/UIButton+Kingfisher.swift

@@ -145,6 +145,9 @@ extension UIButton {
     - parameter completionHandler: Called when the image retrieved and set.
     
     - returns: A task represents the retrieving process.
+     
+    - note: `completionHandler` will be invoked in main thread.
+     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
     */
     public func kf_setImageWithResource(resource: Resource,
                                   forState state: UIControlState,
@@ -165,6 +168,9 @@ extension UIButton {
     - parameter completionHandler: Called when the image retrieved and set.
     
     - returns: A task represents the retrieving process.
+     
+    - note: `completionHandler` will be invoked in main thread.
+     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
     */
     public func kf_setImageWithURL(URL: NSURL,
                         forState state: UIControlState,
@@ -187,6 +193,9 @@ extension UIButton {
     - 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.
     */
     public func kf_setImageWithResource(resource: Resource,
                                   forState state: UIControlState,
@@ -200,14 +209,11 @@ extension UIButton {
         let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo,
             progressBlock: { receivedSize, totalSize in
                 if let progressBlock = progressBlock {
-                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
-                        progressBlock(receivedSize: receivedSize, totalSize: totalSize)
-                    })
+                    progressBlock(receivedSize: receivedSize, totalSize: totalSize)
                 }
             },
             completionHandler: {[weak self] image, error, cacheType, imageURL in
-                
-                dispatch_async_safely_main_queue {
+                dispatch_async_safely_to_main_queue {
                     if let sSelf = self {
                         
                         sSelf.kf_setImageTask(nil)
@@ -235,6 +241,9 @@ extension UIButton {
     - 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.
     */
     public func kf_setImageWithURL(URL: NSURL,
                         forState state: UIControlState,
@@ -410,6 +419,9 @@ extension UIButton {
     - parameter completionHandler: Called when the image retrieved and set.
     
     - returns: A task represents the retrieving process.
+     
+    - note: `completionHandler` will be invoked in main thread.
+     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
     */
     public func kf_setBackgroundImageWithResource(resource: Resource,
                                             forState state: UIControlState,
@@ -430,6 +442,9 @@ extension UIButton {
     - parameter completionHandler: Called when the image retrieved and set.
     
     - returns: A task represents the retrieving process.
+     
+    - note: `completionHandler` will be invoked in main thread.
+     The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
     */
     public func kf_setBackgroundImageWithURL(URL: NSURL,
                                   forState state: UIControlState,
@@ -452,6 +467,9 @@ extension UIButton {
     - 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.
     */
     public func kf_setBackgroundImageWithResource(resource: Resource,
                                             forState state: UIControlState,
@@ -465,14 +483,11 @@ extension UIButton {
         let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo,
             progressBlock: { receivedSize, totalSize in
                 if let progressBlock = progressBlock {
-                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
-                        progressBlock(receivedSize: receivedSize, totalSize: totalSize)
-                    })
+                    progressBlock(receivedSize: receivedSize, totalSize: totalSize)
                 }
             },
             completionHandler: { [weak self] image, error, cacheType, imageURL in
-                dispatch_async_safely_main_queue {
-                    
+                dispatch_async_safely_to_main_queue {
                     if let sSelf = self {
                         
                         sSelf.kf_setBackgroundImageTask(nil)
@@ -501,6 +516,9 @@ extension UIButton {
     - 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.
     */
     public func kf_setBackgroundImageWithURL(URL: NSURL,
                                   forState state: UIControlState,

+ 20 - 0
Tests/KingfisherTests/ImageViewExtensionTests.swift

@@ -70,6 +70,7 @@ class ImageViewExtensionTests: XCTestCase {
         
         imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
             progressBlockIsCalled = true
+            XCTAssertTrue(NSThread.isMainThread())
         }) { (image, error, cacheType, imageURL) -> () in
             expectation.fulfill()
             
@@ -80,6 +81,25 @@ class ImageViewExtensionTests: XCTestCase {
             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.")
+            XCTAssertTrue(NSThread.isMainThread())
+        }
+        
+        waitForExpectationsWithTimeout(5, handler: nil)
+    }
+    
+    func testImageDownloadCompletionHandlerRunningOnMainQueue() {
+        let expectation = expectationWithDescription("wait for downloading image")
+        
+        let URLString = testKeys[0]
+        stubRequest("GET", URLString).andReturn(200).withBody(testImageData)
+        let URL = NSURL(string: URLString)!
+        
+        let customQueue = dispatch_queue_create("com.kingfisher.testQueue", DISPATCH_QUEUE_SERIAL)
+        imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: [.CallbackDispatchQueue(customQueue)], progressBlock: { (receivedSize, totalSize) -> () in
+            XCTAssertTrue(NSThread.isMainThread())
+        }) { (image, error, cacheType, imageURL) -> () in
+            XCTAssertTrue(NSThread.isMainThread(), "The image extension callback should be always in main queue.")
+            expectation.fulfill()
         }
         
         waitForExpectationsWithTimeout(5, handler: nil)

+ 74 - 0
Tests/KingfisherTests/KingfisherManagerTests.swift

@@ -129,4 +129,78 @@ class KingfisherManagerTests: XCTestCase {
         
         waitForExpectationsWithTimeout(5, handler: nil)
     }
+    
+    func testSuccessCompletionHandlerRunningOnMainQueueDefaultly() {
+        let progressExpectation = expectationWithDescription("progressBlock running on main queue")
+        let completionExpectation = expectationWithDescription("completionHandler running on main queue")
+        let URLString = testKeys[0]
+        stubRequest("GET", URLString).andReturn(200).withBody(testImageData)
+        
+        let URL = NSURL(string: URLString)!
+        
+        manager.retrieveImageWithURL(URL, optionsInfo: nil, progressBlock: { _, _ in
+            XCTAssertTrue(NSThread.isMainThread())
+            progressExpectation.fulfill()
+            }, completionHandler: { _, error, _, _ in
+                XCTAssertNil(error)
+                XCTAssertTrue(NSThread.isMainThread())
+                completionExpectation.fulfill()
+        })
+        waitForExpectationsWithTimeout(5, handler: nil)
+    }
+    
+    func testErrorCompletionHandlerRunningOnMainQueueDefaultly() {
+        let expectation = expectationWithDescription("running on main queue")
+        let URLString = testKeys[0]
+        stubRequest("GET", URLString).andReturn(404)
+        
+        let URL = NSURL(string: URLString)!
+        
+        manager.retrieveImageWithURL(URL, optionsInfo: nil, progressBlock: { _, _ in
+            //won't be called
+            }, completionHandler: { _, error, _, _ in
+                XCTAssertNotNil(error)
+                XCTAssertTrue(NSThread.isMainThread())
+                expectation.fulfill()
+        })
+        waitForExpectationsWithTimeout(5, handler: nil)
+    }
+    
+    func testSucessCompletionHandlerRunningOnCustomQueue() {
+        let progressExpectation = expectationWithDescription("progressBlock running on custom queue")
+        let completionExpectation = expectationWithDescription("completionHandler running on custom queue")
+        let URLString = testKeys[0]
+        stubRequest("GET", URLString).andReturn(200).withBody(testImageData)
+        
+        let URL = NSURL(string: URLString)!
+        
+        let customQueue = dispatch_queue_create("com.kingfisher.testQueue", DISPATCH_QUEUE_SERIAL)
+        manager.retrieveImageWithURL(URL, optionsInfo: [.CallbackDispatchQueue(customQueue)], progressBlock: { _, _ in
+            XCTAssertTrue(NSThread.isMainThread())
+            progressExpectation.fulfill()
+            }, completionHandler: { _, error, _, _ in
+                XCTAssertNil(error)
+                XCTAssertEqual(String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL))!, "com.kingfisher.testQueue")
+                completionExpectation.fulfill()
+        })
+        waitForExpectationsWithTimeout(5, handler: nil)
+    }
+    
+    func testErrorCompletionHandlerRunningOnCustomQueue() {
+        let expectation = expectationWithDescription("running on custom queue")
+        let URLString = testKeys[0]
+        stubRequest("GET", URLString).andReturn(404)
+        
+        let URL = NSURL(string: URLString)!
+        
+        let customQueue = dispatch_queue_create("com.kingfisher.testQueue", DISPATCH_QUEUE_SERIAL)
+        manager.retrieveImageWithURL(URL, optionsInfo: [.CallbackDispatchQueue(customQueue)], progressBlock: { _, _ in
+            //won't be called
+            }, completionHandler: { _, error, _, _ in
+                XCTAssertNotNil(error)
+                XCTAssertEqual(String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL))!, "com.kingfisher.testQueue")
+                expectation.fulfill()
+        })
+        waitForExpectationsWithTimeout(5, handler: nil)
+    }
 }