Jelajahi Sumber

move queue dispatch code into ImageDownloader

weizhou 10 tahun lalu
induk
melakukan
33a1025833

+ 11 - 4
Sources/ImageDownloader.swift

@@ -127,11 +127,12 @@ public class ImageDownloader: NSObject {
     /// Use this to set supply a configuration for the downloader. By default, NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used. You could change the configuration before a downloaing task starts. A configuration without persistent storage for caches is requsted for downloader working correctly.
     public var sessionConfiguration = NSURLSessionConfiguration.ephemeralSessionConfiguration() {
         didSet {
-            session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
+            session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: sessionDelegationQueue)
         }
     }
     
     private var session: NSURLSession?
+    private let sessionDelegationQueue = NSOperationQueue()
     
     /// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more.
     public weak var delegate: ImageDownloaderDelegate?
@@ -167,7 +168,8 @@ public class ImageDownloader: NSObject {
         
         super.init()
         
-        session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
+        sessionDelegationQueue.maxConcurrentOperationCount = 1
+        session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: sessionDelegationQueue)
     }
     
     func fetchLoadForKey(key: NSURL) -> ImageFetchLoad? {
@@ -318,7 +320,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 +359,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(options.callbackDispatchQueue, { () -> Void in
+                    callbackPair.completionHander?(image: image, error: error, imageURL: imageURL, originalData: originalData)
+                })
             }
         }
     }

+ 23 - 29
Sources/ImageView+Kingfisher.swift

@@ -199,32 +199,27 @@ 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 {
-                    
-                    guard let sSelf = self where imageURL == sSelf.kf_webURL else {
-                        completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
-                        return
-                    }
-                    
-                    sSelf.kf_setImageTask(nil)
-                    
-                    guard let image = image else {
-                        indicator?.kf_stopAnimating()
-                        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 {
+                guard let sSelf = self where imageURL == sSelf.kf_webURL else {
+                    completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
+                    return
+                }
+                
+                sSelf.kf_setImageTask(nil)
+                
+                guard let image = image else {
+                    indicator?.kf_stopAnimating()
+                    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: {
@@ -239,14 +234,13 @@ extension ImageView {
                                         completion: { finished in
                                             transition.completion?(finished)
                                             completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
-                                        })
-                                })
+                                    })
+                            })
 #endif
-                    } else {
-                        indicator?.kf_stopAnimating()
-                        sSelf.image = image
-                        completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
-                    }
+                } else {
+                    indicator?.kf_stopAnimating()
+                    sSelf.image = image
+                    completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
                 }
             })
         

+ 16 - 24
Sources/UIButton+Kingfisher.swift

@@ -200,23 +200,19 @@ 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 {
-                    if let sSelf = self {
-                        
-                        sSelf.kf_setImageTask(nil)
-                        
-                        if imageURL == sSelf.kf_webURLForState(state) && image != nil {
-                            sSelf.setImage(image, forState: state)
-                        }
-                        completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
+                if let sSelf = self {
+                    
+                    sSelf.kf_setImageTask(nil)
+                    
+                    if imageURL == sSelf.kf_webURLForState(state) && image != nil {
+                        sSelf.setImage(image, forState: state)
                     }
+                    completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
                 }
             })
         
@@ -465,23 +461,19 @@ 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 {
+                
+                if let sSelf = self {
+                    
+                    sSelf.kf_setBackgroundImageTask(nil)
                     
-                    if let sSelf = self {
-                        
-                        sSelf.kf_setBackgroundImageTask(nil)
-                        
-                        if imageURL == sSelf.kf_backgroundWebURLForState(state) && image != nil {
-                            sSelf.setBackgroundImage(image, forState: state)
-                        }
-                        completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
+                    if imageURL == sSelf.kf_backgroundWebURLForState(state) && image != nil {
+                        sSelf.setBackgroundImage(image, forState: state)
                     }
+                    completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
                 }
             })
         

+ 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 testImageDownloadCompletionHandlerRunningOnCustomQueue() {
+        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
+            XCTAssertEqual(String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL))!, "com.kingfisher.testQueue")
+            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)
+    }
 }