Pārlūkot izejas kodu

Use UInt based view setting identifier to prevent unexpected callback

onevcat 7 gadi atpakaļ
vecāks
revīzija
d391ba8308

+ 17 - 10
Sources/Extensions/ImageView+Kingfisher.swift

@@ -59,7 +59,8 @@ extension KingfisherWrapper where Base: ImageView {
         let maybeIndicator = indicator
         maybeIndicator?.startAnimatingView()
 
-        mutatingSelf.taskIdentifier = source.identifier
+        let issuedIdentifier = issueSourceIdentifier()
+        mutatingSelf.taskIdentifier = issuedIdentifier
 
         if base.shouldPreloadAllAnimation() {
             options.preloadAllAnimationData = true
@@ -69,7 +70,7 @@ extension KingfisherWrapper where Base: ImageView {
             with: source,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard source.identifier == self.taskIdentifier else { return }
+                guard issuedIdentifier == self.taskIdentifier else { return }
                 if let progressBlock = progressBlock {
                     progressBlock(receivedSize, totalSize)
                 }
@@ -77,9 +78,9 @@ extension KingfisherWrapper where Base: ImageView {
             completionHandler: { result in
                 CallbackQueue.mainCurrentOrAsync.execute {
                     maybeIndicator?.stopAnimatingView()
-                    guard source.identifier == self.taskIdentifier else {
+                    guard issuedIdentifier == self.taskIdentifier else {
                         let error = KingfisherError.imageSettingError(
-                            reason: .notCurrentSource(result: result.value, error: result.error, source: source))
+                            reason: .notCurrentSourceTask(result: result.value, error: result.error, source: source))
                         completionHandler?(.failure(error))
                         return
                     }
@@ -209,9 +210,15 @@ private var imageTaskKey: Void?
 
 extension KingfisherWrapper where Base: ImageView {
 
-    public private(set) var taskIdentifier: String? {
-        get { return getAssociatedObject(base, &taskIdentifierKey) }
-        set { setRetainedAssociatedObject(base, &taskIdentifierKey, newValue) }
+    public private(set) var taskIdentifier: SourceIdentifier? {
+        get {
+            let box: Box<SourceIdentifier>? = getAssociatedObject(base, &taskIdentifierKey)
+            return box?.value
+        }
+        set {
+            let box = newValue.map { Box($0) }
+            setRetainedAssociatedObject(base, &taskIdentifierKey, box)
+        }
     }
 
     /// Holds which indicator type is going to be used.
@@ -301,9 +308,9 @@ extension KingfisherWrapper where Base: ImageView {
 
 extension KingfisherWrapper where Base: ImageView {
     /// Gets the image URL binded to this image view.
-    @available(*, deprecated, message: "Use `taskIdentifier` instead.", renamed: "taskIdentifier")
+    @available(*, obsoleted: 5.0, message: "Use `taskIdentifier` instead to identify a setting task.")
     public private(set) var webURL: URL? {
-        get { return taskIdentifier.flatMap { URL(string: $0) } }
-        set { taskIdentifier = newValue?.absoluteString }
+        get { return nil }
+        set { }
     }
 }

+ 34 - 20
Sources/Extensions/NSButton+Kingfisher.swift

@@ -50,20 +50,21 @@ extension KingfisherWrapper where Base: NSButton {
             base.image = placeholder
         }
 
-        mutatingSelf.taskIdentifier = source.identifier
+        let issuedIdentifier = issueSourceIdentifier()
+        mutatingSelf.taskIdentifier = issuedIdentifier
 
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard source.identifier == self.taskIdentifier else { return }
+                guard issuedIdentifier == self.taskIdentifier else { return }
                 progressBlock?(receivedSize, totalSize)
             },
             completionHandler: { result in
                 DispatchQueue.main.safeAsync {
-                    guard source.identifier == self.taskIdentifier else {
+                    guard issuedIdentifier == self.taskIdentifier else {
                         let error = KingfisherError.imageSettingError(
-                            reason: .notCurrentSource(result: result.value, error: result.error, source: source))
+                            reason: .notCurrentSourceTask(result: result.value, error: result.error, source: source))
                         completionHandler?(.failure(error))
                         return
                     }
@@ -147,19 +148,20 @@ extension KingfisherWrapper where Base: NSButton {
             base.alternateImage = placeholder
         }
 
-        mutatingSelf.alternateTaskIdentifier = source.identifier
+        let issuedIdentifier = issueSourceIdentifier()
+        mutatingSelf.alternateTaskIdentifier = issuedIdentifier
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard self.alternateTaskIdentifier == source.identifier else { return }
+                guard issuedIdentifier == self.alternateTaskIdentifier else { return }
                 progressBlock?(receivedSize, totalSize)
             },
             completionHandler: { result in
                 CallbackQueue.mainCurrentOrAsync.execute {
-                    guard self.alternateTaskIdentifier == source.identifier else {
+                    guard issuedIdentifier == self.alternateTaskIdentifier else {
                         let error = KingfisherError.imageSettingError(
-                            reason: .notCurrentSource(result: result.value, error: result.error, source: source))
+                            reason: .notCurrentSourceTask(result: result.value, error: result.error, source: source))
                         completionHandler?(.failure(error))
                         return
                     }
@@ -233,9 +235,15 @@ private var alternateImageTaskKey: Void?
 
 extension KingfisherWrapper where Base: NSButton {
 
-    public private(set) var taskIdentifier: String? {
-        get { return getAssociatedObject(base, &taskIdentifierKey) }
-        set { setRetainedAssociatedObject(base, &taskIdentifierKey, newValue) }
+    public private(set) var taskIdentifier: SourceIdentifier? {
+        get {
+            let box: Box<SourceIdentifier>? = getAssociatedObject(base, &taskIdentifierKey)
+            return box?.value
+        }
+        set {
+            let box = newValue.map { Box($0) }
+            setRetainedAssociatedObject(base, &taskIdentifierKey, box)
+        }
     }
     
     private var imageTask: DownloadTask? {
@@ -243,9 +251,15 @@ extension KingfisherWrapper where Base: NSButton {
         set { setRetainedAssociatedObject(base, &imageTaskKey, newValue)}
     }
 
-    public private(set) var alternateTaskIdentifier: String? {
-        get { return getAssociatedObject(base, &alternateTaskIdentifierKey) }
-        set { setRetainedAssociatedObject(base, &alternateTaskIdentifierKey, newValue) }
+    public private(set) var alternateTaskIdentifier: SourceIdentifier? {
+        get {
+            let box: Box<SourceIdentifier>? = getAssociatedObject(base, &alternateTaskIdentifierKey)
+            return box?.value
+        }
+        set {
+            let box = newValue.map { Box($0) }
+            setRetainedAssociatedObject(base, &alternateTaskIdentifierKey, box)
+        }
     }
 
     private var alternateImageTask: DownloadTask? {
@@ -257,17 +271,17 @@ extension KingfisherWrapper where Base: NSButton {
 extension KingfisherWrapper where Base: NSButton {
 
     /// Gets the image URL binded to this button.
-    @available(*, deprecated, message: "Use `taskIdentifier` instead.", renamed: "taskIdentifier")
+    @available(*, obsoleted: 5.0, message: "Use `taskIdentifier` instead to identify a setting task.")
     public private(set) var webURL: URL? {
-        get { return taskIdentifier.flatMap { URL(string: $0) } }
-        set { taskIdentifier = newValue?.absoluteString }
+        get { return nil }
+        set { }
     }
 
 
     /// Gets the image URL binded to this button.
-    @available(*, deprecated, message: "Use `alternateTaskIdentifier` instead.", renamed: "alternateTaskIdentifier")
+    @available(*, obsoleted: 5.0, message: "Use `alternateTaskIdentifier` instead to identify a setting task.")
     public private(set) var alternateWebURL: URL? {
-        get { return alternateTaskIdentifier.flatMap { URL(string: $0) } }
-        set { alternateTaskIdentifier = newValue?.absoluteString }
+        get { return nil }
+        set { }
     }
 }

+ 22 - 20
Sources/Extensions/UIButton+Kingfisher.swift

@@ -50,19 +50,20 @@ extension KingfisherWrapper where Base: UIButton {
         }
         
         var mutatingSelf = self
-        setTaskIdentifier(source.identifier, for: state)
+        let issuedTaskIdentifier = issueSourceIdentifier()
+        setTaskIdentifier(issuedTaskIdentifier, for: state)
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard source.identifier == self.taskIdentifier(for: state) else { return }
+                guard issuedTaskIdentifier == self.taskIdentifier(for: state) else { return }
                 progressBlock?(receivedSize, totalSize)
             },
             completionHandler: { result in
                 CallbackQueue.mainCurrentOrAsync.execute {
-                    guard source.identifier == self.taskIdentifier(for: state) else {
+                    guard issuedTaskIdentifier == self.taskIdentifier(for: state) else {
                         let error = KingfisherError.imageSettingError(
-                            reason: .notCurrentSource(result: result.value, error: result.error, source: source))
+                            reason: .notCurrentSourceTask(result: result.value, error: result.error, source: source))
                         completionHandler?(.failure(error))
                         return
                     }
@@ -149,12 +150,13 @@ extension KingfisherWrapper where Base: UIButton {
         }
         
         var mutatingSelf = self
-        setBackgroundTaskIdentifier(source.identifier, for: state)
+        let issuedTaskIdentifier = issueSourceIdentifier()
+        setBackgroundTaskIdentifier(issuedTaskIdentifier, for: state)
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard source.identifier == self.backgroundTaskIdentifier(for: state) else {
+                guard issuedTaskIdentifier == self.backgroundTaskIdentifier(for: state) else {
                     return
                 }
                 if let progressBlock = progressBlock {
@@ -163,9 +165,9 @@ extension KingfisherWrapper where Base: UIButton {
             },
             completionHandler: { result in
                 CallbackQueue.mainCurrentOrAsync.execute {
-                    guard source.identifier == self.backgroundTaskIdentifier(for: state) else {
+                    guard issuedTaskIdentifier == self.backgroundTaskIdentifier(for: state) else {
                         let error = KingfisherError.imageSettingError(
-                            reason: .notCurrentSource(result: result.value, error: result.error, source: source))
+                            reason: .notCurrentSourceTask(result: result.value, error: result.error, source: source))
                         completionHandler?(.failure(error))
                         return
                     }
@@ -236,12 +238,12 @@ private var imageTaskKey: Void?
 
 extension KingfisherWrapper where Base: UIButton {
 
-    public func taskIdentifier(for state: UIControl.State) -> String? {
-        return taskIdentifierInfo[NSNumber(value:state.rawValue)] as? String
+    public func taskIdentifier(for state: UIControl.State) -> SourceIdentifier? {
+        return (taskIdentifierInfo[NSNumber(value:state.rawValue)] as? Box<SourceIdentifier>)?.value
     }
 
-    private func setTaskIdentifier(_ identifier: String?, for state: UIControl.State) {
-        taskIdentifierInfo[NSNumber(value:state.rawValue)] = identifier
+    private func setTaskIdentifier(_ identifier: SourceIdentifier?, for state: UIControl.State) {
+        taskIdentifierInfo[NSNumber(value:state.rawValue)] = identifier.map { Box($0) }
     }
     
     private var taskIdentifierInfo: NSMutableDictionary {
@@ -271,12 +273,12 @@ private var backgroundImageTaskKey: Void?
 
 extension KingfisherWrapper where Base: UIButton {
 
-    public func backgroundTaskIdentifier(for state: UIControl.State) -> String? {
-        return backgroundTaskIdentifierInfo[NSNumber(value:state.rawValue)] as? String
+    public func backgroundTaskIdentifier(for state: UIControl.State) -> SourceIdentifier? {
+        return (backgroundTaskIdentifierInfo[NSNumber(value:state.rawValue)] as? Box<SourceIdentifier>)?.value
     }
     
-    private func setBackgroundTaskIdentifier(_ identifier: String?, for state: UIControl.State) {
-        backgroundTaskIdentifierInfo[NSNumber(value:state.rawValue)] = identifier
+    private func setBackgroundTaskIdentifier(_ identifier: SourceIdentifier?, for state: UIControl.State) {
+        backgroundTaskIdentifierInfo[NSNumber(value:state.rawValue)] = identifier.map { Box($0) }
     }
     
     private var backgroundTaskIdentifierInfo: NSMutableDictionary {
@@ -306,17 +308,17 @@ extension KingfisherWrapper where Base: UIButton {
     ///
     /// - Parameter state: The state that uses the specified image.
     /// - Returns: Current URL for image.
-    @available(*, deprecated, message: "Use `taskIdentifier` instead.", renamed: "taskIdentifier")
+    @available(*, obsoleted: 5.0, message: "Use `taskIdentifier` instead to identify a setting task.")
     public func webURL(for state: UIControl.State) -> URL? {
-        return taskIdentifier(for: state).flatMap { URL(string: $0) }
+        return nil
     }
 
     /// Gets the background image URL of this button for a specified state.
     ///
     /// - Parameter state: The state that uses the specified background image.
     /// - Returns: Current URL for image.
-    @available(*, deprecated, message: "Use `backgroundTaskIdentifier` instead.", renamed: "backgroundTaskIdentifier")
+    @available(*, obsoleted: 5.0, message: "Use `backgroundTaskIdentifier` instead to identify a setting task.")
     public func backgroundWebURL(for state: UIControl.State) -> URL? {
-        return backgroundTaskIdentifier(for: state).flatMap { URL(string: $0) }
+        return nil
     }
 }

+ 17 - 10
Sources/Extensions/WKInterfaceImage+Kingfisher.swift

@@ -49,19 +49,20 @@ extension KingfisherWrapper where Base: WKInterfaceImage {
             base.setImage(placeholder)
         }
         
-        mutatingSelf.taskIdentifier = source.identifier
+        let issuedTaskIdentifier = issueSourceIdentifier()
+        mutatingSelf.taskIdentifier = issuedTaskIdentifier
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             progressBlock: { receivedSize, totalSize in
-                guard source.identifier == self.taskIdentifier else { return }
+                guard issuedTaskIdentifier == self.taskIdentifier else { return }
                 progressBlock?(receivedSize, totalSize)
             },
             completionHandler: { result in
                 CallbackQueue.mainCurrentOrAsync.execute {
-                    guard source.identifier == self.taskIdentifier else {
+                    guard issuedTaskIdentifier == self.taskIdentifier else {
                         let error = KingfisherError.imageSettingError(
-                            reason: .notCurrentSource(result: result.value, error: result.error, source: source))
+                            reason: .notCurrentSourceTask(result: result.value, error: result.error, source: source))
                         completionHandler?(.failure(error))
                         return
                     }
@@ -133,9 +134,15 @@ private var imageTaskKey: Void?
 
 extension KingfisherWrapper where Base: WKInterfaceImage {
     
-    public private(set) var taskIdentifier: String? {
-        get { return getAssociatedObject(base, &taskIdentifierKey) }
-        set { setRetainedAssociatedObject(base, &taskIdentifierKey, newValue) }
+    public private(set) var taskIdentifier: SourceIdentifier? {
+        get {
+            let box: Box<SourceIdentifier>? = getAssociatedObject(base, &taskIdentifierKey)
+            return box?.value
+        }
+        set {
+            let box = newValue.map { Box($0) }
+            setRetainedAssociatedObject(base, &taskIdentifierKey, box)
+        }
     }
 
     private var imageTask: DownloadTask? {
@@ -146,9 +153,9 @@ extension KingfisherWrapper where Base: WKInterfaceImage {
 
 extension KingfisherWrapper where Base: WKInterfaceImage {
     /// Gets the image URL binded to this image view.
-    @available(*, deprecated, message: "Use `taskIdentifier` instead.", renamed: "taskIdentifier")
+    @available(*, obsoleted: 5.0, message: "Use `taskIdentifier` instead to identify a setting task.")
     public private(set) var webURL: URL? {
-        get { return taskIdentifier.flatMap { URL(string: $0) } }
-        set { taskIdentifier = newValue?.absoluteString }
+        get { return nil }
+        set { }
     }
 }

+ 0 - 6
Sources/General/ImageSource/Resource.swift

@@ -35,12 +35,6 @@ public protocol Resource {
     
     /// The target image URL.
     var downloadURL: URL { get }
-
-    var identifier: String { get }
-}
-
-extension Resource {
-    public var identifier: String { return downloadURL.absoluteString }
 }
 
 /// ImageResource is a simple combination of `downloadURL` and `cacheKey`.

+ 7 - 7
Sources/General/ImageSource/Source.swift

@@ -26,17 +26,17 @@
 
 import Foundation
 
+public typealias SourceIdentifier = UInt
+var currentIdentifier: SourceIdentifier = 0
+func issueSourceIdentifier() -> SourceIdentifier {
+    currentIdentifier += 1
+    return currentIdentifier
+}
+
 public enum Source {
     case network(Resource)
     case provider(ImageDataProvider)
     
-    var identifier: String {
-        switch self {
-        case .network(let resource): return resource.identifier
-        case .provider(let provider): return provider.identifier
-        }
-    }
-    
     var cacheKey: String {
         switch self {
         case .network(let resource): return resource.cacheKey

+ 13 - 6
Sources/General/KingfisherError.swift

@@ -156,7 +156,7 @@ public enum KingfisherError: Error {
     /// Represnts the error reason duting image setting in a view related class.
     ///
     /// - emptySource: The input resource is empty or `nil`. Code 5001.
-    /// - notCurrentSource: The source task is finished, but it is not the one expected now. Code 5002.
+    /// - notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002.
     /// - dataProviderError: An error happens during getting data from an `ImageDataProvider`. Code 5003.
     public enum ImageSettingErrorReason {
         
@@ -165,9 +165,9 @@ public enum KingfisherError: Error {
         
         /// The resource task is finished, but it is not the one expected now. This usually happens when you set another
         /// resource on the view without cancelling the current on-going one. The previous setting task will fail with
-        /// this `.notCurrentSource` error when a result got, regardless of it being successful or not for that task.
+        /// this `.notCurrentSourceTask` error when a result got, regardless of it being successful or not for that task.
         /// Code 5002.
-        case notCurrentSource(result: RetrieveImageResult?, error: Error?, source: Source)
+        case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source)
 
         /// An error happens during getting data from an `ImageDataProvider`. Code 5003.
         case dataProviderError(provider: ImageDataProvider, error: Error)
@@ -204,6 +204,13 @@ public enum KingfisherError: Error {
         }
         return false
     }
+    
+    public var isNotCurrentTask: Bool {
+        if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self {
+            return true
+        }
+        return false
+    }
 }
 
 extension KingfisherError: LocalizedError {
@@ -338,9 +345,9 @@ extension KingfisherError.ImageSettingErrorReason {
         switch self {
         case .emptySource:
             return "The input resource is empty."
-        case .notCurrentSource(let result, let error, let resource):
+        case .notCurrentSourceTask(let result, let error, let resource):
             if let result = result {
-                return "Retrieving resource succeeded, but this resource is " +
+                return "Retrieving resource succeeded, but this source is " +
                        "not the one currently expected. Result: \(result). Resource: \(resource)."
             } else if let error = error {
                 return "Retrieving resource failed, and this resource is " +
@@ -356,7 +363,7 @@ extension KingfisherError.ImageSettingErrorReason {
     var errorCode: Int {
         switch self {
         case .emptySource: return 5001
-        case .notCurrentSource: return 5002
+        case .notCurrentSourceTask: return 5002
         case .dataProviderError: return 5003
         }
     }

+ 38 - 4
Tests/KingfisherTests/ImageViewExtensionTests.swift

@@ -80,7 +80,7 @@ class ImageViewExtensionTests: XCTestCase {
             let value = result.value!
             XCTAssertTrue(value.image.renderEqual(to: testImage))
             XCTAssertTrue(self.imageView.image!.renderEqual(to: testImage))
-            XCTAssertEqual(self.imageView.kf.taskIdentifier, url.absoluteString)
+            XCTAssertEqual(self.imageView.kf.taskIdentifier, issueSourceIdentifier() - 1)
             
             XCTAssertEqual(value.cacheType, .none)
             XCTAssertTrue(Thread.isMainThread)
@@ -127,7 +127,7 @@ class ImageViewExtensionTests: XCTestCase {
             let value = result.value!
             XCTAssertTrue(value.image.renderEqual(to: testImage))
             XCTAssertTrue(self.imageView.image!.renderEqual(to: testImage))
-            XCTAssertEqual(self.imageView.kf.taskIdentifier, url.absoluteString)
+            XCTAssertEqual(self.imageView.kf.taskIdentifier, issueSourceIdentifier() - 1)
 
             XCTAssertEqual(value.cacheType, .none)
             XCTAssertTrue(Thread.isMainThread)
@@ -179,7 +179,8 @@ class ImageViewExtensionTests: XCTestCase {
         }
         
         group.enter()
-        imageView.kf.setImage(with: url){ result in
+        let anotherImageView = ImageView()
+        anotherImageView.kf.setImage(with: url) { result in
             XCTAssertNotNil(result.value)
             group.leave()
         }
@@ -364,7 +365,7 @@ class ImageViewExtensionTests: XCTestCase {
             // The download successed, but not the resource we want.
             XCTAssertNotNil(result.error)
             if case .imageSettingError(
-                reason: .notCurrentSource(let result, _, let source)) = result.error!
+                reason: .notCurrentSourceTask(let result, _, let source)) = result.error!
             {
                 XCTAssertEqual(source.url, testURLs[0])
                 XCTAssertNotEqual(result!.image, self.imageView.image)
@@ -618,6 +619,39 @@ class ImageViewExtensionTests: XCTestCase {
         XCTAssertEqual(testImage, imageView.image)
         waitForExpectations(timeout: 5, handler: nil)
     }
+    
+    // https://github.com/onevcat/Kingfisher/issues/1053
+    func testSetSameURLWithDifferentProcessors() {
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        
+        stub(url, data: testImageData)
+        
+        let size1 = CGSize(width: 10, height: 10)
+        let p1 = ResizingImageProcessor(referenceSize: size1)
+        
+        let size2 = CGSize(width: 20, height: 20)
+        let p2 = ResizingImageProcessor(referenceSize: size2)
+        
+        let group = DispatchGroup()
+        
+        group.enter()
+        imageView.kf.setImage(with: url, options: [.processor(p1)]) { result in
+            XCTAssertNotNil(result.error)
+            XCTAssertTrue(result.error!.isNotCurrentTask)
+            group.leave()
+        }
+        
+        group.enter()
+        imageView.kf.setImage(with: url, options: [.processor(p2)]) { result in
+            XCTAssertNotNil(result.value)
+            XCTAssertEqual(result.value!.image.size, size2)
+            group.leave()
+        }
+        
+        group.notify(queue: .main) { exp.fulfill() }
+        waitForExpectations(timeout: 1, handler: nil)
+    }
 }
 
 extension View: Placeholder {}

+ 2 - 2
Tests/KingfisherTests/NSButtonExtensionTests.swift

@@ -76,7 +76,7 @@ class NSButtonExtensionTests: XCTestCase {
             XCTAssertNotNil(image)
             XCTAssertTrue(image!.renderEqual(to: testImage))
             XCTAssertTrue(self.button.image!.renderEqual(to: testImage))
-            XCTAssertEqual(result.value?.imageURL?.absoluteString, self.button.kf.taskIdentifier)
+            XCTAssertEqual(self.button.kf.taskIdentifier, issueSourceIdentifier() - 1)
             XCTAssertEqual(result.value!.cacheType, .none)
             
             exp.fulfill()
@@ -98,7 +98,7 @@ class NSButtonExtensionTests: XCTestCase {
             XCTAssertNotNil(image)
             XCTAssertTrue(image!.renderEqual(to: testImage))
             XCTAssertTrue(self.button.alternateImage!.renderEqual(to: testImage))
-            XCTAssertEqual(result.value?.imageURL?.absoluteString, self.button.kf.alternateTaskIdentifier)
+            XCTAssertEqual(self.button.kf.alternateTaskIdentifier, issueSourceIdentifier() - 1)
             XCTAssertEqual(result.value!.cacheType, .none)
             
             exp.fulfill()

+ 2 - 1
Tests/KingfisherTests/UIButtonExtensionTests.swift

@@ -78,7 +78,8 @@ class UIButtonExtensionTests: XCTestCase {
             XCTAssertTrue(image!.renderEqual(to: testImage))
             XCTAssertTrue(self.button.image(for: .normal)!.renderEqual(to: testImage))
             
-            XCTAssertEqual(self.button.kf.taskIdentifier(for: .normal), result.value!.imageURL?.absoluteString)
+            let issuedTaskIdentifier = issueSourceIdentifier()
+            XCTAssertEqual(self.button.kf.taskIdentifier(for: .normal), issuedTaskIdentifier - 1)
             XCTAssertEqual(result.value!.cacheType, .none)
             
             exp.fulfill()