Skip to content

Commit

Permalink
Fix cancel issues
Browse files Browse the repository at this point in the history
  • Loading branch information
onevcat committed Sep 10, 2015
1 parent 4cf52c7 commit e271e3e
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 4 deletions.
6 changes: 6 additions & 0 deletions Kingfisher/ImageDownloader.swift
Expand Up @@ -174,6 +174,10 @@ public extension ImageDownloader {
progressBlock: ImageDownloaderProgressBlock?,
completionHandler: ImageDownloaderCompletionHandler?)
{
if let retrieveImageTask = retrieveImageTask where retrieveImageTask.cancelled {
return
}

let timeout = self.downloadTimeout == 0.0 ? 15.0 : self.downloadTimeout

// We need to set the URL as the load key. So before setup progress, we need to ask the `requestModifier` for a final URL.
Expand Down Expand Up @@ -237,6 +241,7 @@ 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) {

completionHandler(NSURLSessionResponseDisposition.Allow)
}

Expand All @@ -247,6 +252,7 @@ extension ImageDownloader: NSURLSessionDataDelegate {

if let URL = dataTask.originalRequest?.URL, fetchLoad = fetchLoadForKey(URL) {
fetchLoad.responseData.appendData(data)

for callbackPair in fetchLoad.callbacks {
callbackPair.progressBlock?(receivedSize: Int64(fetchLoad.responseData.length), totalSize: dataTask.response!.expectedContentLength)
}
Expand Down
15 changes: 12 additions & 3 deletions Kingfisher/KingfisherManager.swift
Expand Up @@ -33,20 +33,29 @@ public typealias CompletionHandler = ((image: UIImage?, error: NSError?, cacheTy
/// It contains an async task of getting image from disk and from network.
public class RetrieveImageTask {

// If task is canceled before the download task started (which means the `downloadTask` is nil),
// the download task should not begin.
var cancelled: Bool = false

var diskRetrieveTask: RetrieveImageDiskTask?
var downloadTask: RetrieveImageDownloadTask?

/**
Cancel current task. If this task does not begin or already done, do nothing.
*/
public func cancel() {
if let diskRetrieveTask = diskRetrieveTask {
dispatch_block_cancel(diskRetrieveTask)
}
// From Xcode 7 beta 6, the `dispatch_block_cancel` will crash at runtime.
// So we removed disk retrieve canceling now.
// See https://github.com/onevcat/Kingfisher/issues/99 for more.
// if let diskRetrieveTask = diskRetrieveTask {
// dispatch_block_cancel(diskRetrieveTask)
// }

if let downloadTask = downloadTask {
downloadTask.cancel()
}

cancelled = true
}
}

Expand Down
81 changes: 80 additions & 1 deletion KingfisherTests/UIImageViewExtensionTests.swift
Expand Up @@ -127,6 +127,7 @@ class UIImageViewExtensionTests: XCTestCase {
}) { (image, error, cacheType, imageURL) -> () in
completionBlockIsCalled = true
}

task.cancel()

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) * 0.09)), dispatch_get_main_queue()) { () -> Void in
Expand All @@ -137,8 +138,38 @@ class UIImageViewExtensionTests: XCTestCase {

waitForExpectationsWithTimeout(5, handler: nil)
}

func testImageDownloadCancelForImageViewAfterRequestStarted() {
let expectation = expectationWithDescription("wait for downloading image")

let URLString = testKeys[0]
let stub = stubRequest("GET", URLString).andReturn(200).withBody(testImageData).delay()
let URL = NSURL(string: URLString)!

var progressBlockIsCalled = false
var completionBlockIsCalled = false

let task = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in
progressBlockIsCalled = true
}) { (image, error, cacheType, imageURL) -> () in
completionBlockIsCalled = true
}

dispatch_async(dispatch_get_main_queue()) { () -> Void in
task.cancel()
stub.go()
}

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) * 0.09)), dispatch_get_main_queue()) { () -> Void in
expectation.fulfill()
XCTAssert(progressBlockIsCalled == false, "ProgressBlock should not be called since it is canceled.")
XCTAssert(completionBlockIsCalled == false, "CompletionBlock should not be called since it is canceled.")
}

waitForExpectationsWithTimeout(5, handler: nil)
}

func testImageDownloadCacelPartialTask() {
func testImageDownloadCancelPartialTask() {
let expectation = expectationWithDescription("wait for downloading image")

let URLString = testKeys[0]
Expand Down Expand Up @@ -183,6 +214,54 @@ class UIImageViewExtensionTests: XCTestCase {
waitForExpectationsWithTimeout(5, handler: nil)
}

func testImageDownloadCancelPartialTaskAfterRequestStarted() {
let expectation = expectationWithDescription("wait for downloading image")

let URLString = testKeys[0]
let stub = stubRequest("GET", URLString).andReturn(200).withBody(testImageData).delay()
let URL = NSURL(string: URLString)!

var task1Completion = false
var task2Completion = false
var task3Completion = false

let task1 = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in

}) { (image, error, cacheType, imageURL) -> () in
task1Completion = true
}

let task2 = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in

}) { (image, error, cacheType, imageURL) -> () in
task2Completion = true
}

let task3 = imageView.kf_setImageWithURL(URL, placeholderImage: nil, optionsInfo: nil, progressBlock: { (receivedSize, totalSize) -> () in

}) { (image, error, cacheType, imageURL) -> () in
task3Completion = true
}

// Prevent unused warning.
print(task2)
print(task3)

dispatch_async(dispatch_get_main_queue()) { () -> Void in
task1.cancel()
stub.go()
}

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(Double(NSEC_PER_SEC) * 0.09)), dispatch_get_main_queue()) { () -> Void in
expectation.fulfill()
XCTAssert(task1Completion == false, "Task 1 is canceled. The completion flag should be fasle.")
XCTAssert(task2Completion == true, "Task 2 should be completed.")
XCTAssert(task3Completion == true, "Task 3 should be completed.")
}

waitForExpectationsWithTimeout(5, handler: nil)
}

func testImageDownalodMultipleCaches() {

let cache1 = ImageCache(name: "cache1")
Expand Down

0 comments on commit e271e3e

Please sign in to comment.