Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply callback queue to downloader as well #240

Merged
merged 9 commits into from
Feb 17, 2016
18 changes: 10 additions & 8 deletions Sources/ImageCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand Down
9 changes: 7 additions & 2 deletions Sources/ImageDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}
}
Expand Down Expand Up @@ -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)
})
}
}
}
Expand Down
53 changes: 30 additions & 23 deletions Sources/ImageView+Kingfisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?,
Expand All @@ -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?,
Expand All @@ -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?,
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down
13 changes: 10 additions & 3 deletions Sources/ThreadHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
38 changes: 28 additions & 10 deletions Sources/UIButton+Kingfisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 20 additions & 0 deletions Tests/KingfisherTests/ImageViewExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)
Expand Down
Loading