2
0
Эх сурвалжийг харах

Merge branch 'master' into 6.0

onevcat 5 жил өмнө
parent
commit
eab64cf76d

+ 14 - 0
CHANGELOG.md

@@ -2,6 +2,20 @@
 
 -----
 
+## [5.15.7 - Cancel Lock](https://github.com/onevcat/Kingfisher/releases/tag/5.15.7) (2020-10-29)
+
+#### Fix
+* A potential crash when cancelling image downloading task while accessing its original request on iOS 13 or earlier. [#1558](https://github.com/onevcat/Kingfisher/pull/1558)
+
+---
+
+## [5.15.6 - ImageBinder Callback](https://github.com/onevcat/Kingfisher/releases/tag/5.15.6) (2020-10-11)
+
+#### Fix
+* Prevent main queue dispatching in `ImageBinder` if it is already on main thread. This prevents unintended flickering when reloading. [#1551](https://github.com/onevcat/Kingfisher/pull/1551)
+
+---
+
 ## [5.15.5 - Cancelling Fix](https://github.com/onevcat/Kingfisher/releases/tag/5.15.5) (2020-09-29)
 
 #### Fix

+ 32 - 30
Gemfile.lock

@@ -2,44 +2,43 @@ GEM
   remote: https://rubygems.org/
   specs:
     CFPropertyList (3.0.2)
-    activesupport (4.2.11.3)
-      i18n (~> 0.7)
+    activesupport (5.2.4.4)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      i18n (>= 0.7, < 2)
       minitest (~> 5.1)
-      thread_safe (~> 0.3, >= 0.3.4)
       tzinfo (~> 1.1)
     addressable (2.7.0)
       public_suffix (>= 2.0.2, < 5.0)
-    algoliasearch (1.27.4)
+    algoliasearch (1.27.5)
       httpclient (~> 2.8, >= 2.8.3)
       json (>= 1.5.1)
     atomos (0.1.3)
     aws-eventstream (1.1.0)
-    aws-partitions (1.373.0)
-    aws-sdk-core (3.107.0)
+    aws-partitions (1.388.0)
+    aws-sdk-core (3.109.1)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-partitions (~> 1, >= 1.239.0)
       aws-sigv4 (~> 1.1)
       jmespath (~> 1.0)
-    aws-sdk-kms (1.38.0)
-      aws-sdk-core (~> 3, >= 3.99.0)
+    aws-sdk-kms (1.39.0)
+      aws-sdk-core (~> 3, >= 3.109.0)
       aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.81.0)
-      aws-sdk-core (~> 3, >= 3.104.3)
+    aws-sdk-s3 (1.83.1)
+      aws-sdk-core (~> 3, >= 3.109.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.1)
     aws-sigv4 (1.2.2)
       aws-eventstream (~> 1, >= 1.0.2)
-    babosa (1.0.3)
+    babosa (1.0.4)
     claide (1.0.3)
-    cocoapods (1.9.3)
-      activesupport (>= 4.0.2, < 5)
+    cocoapods (1.10.0)
+      addressable (~> 2.6)
       claide (>= 1.0.2, < 2.0)
-      cocoapods-core (= 1.9.3)
+      cocoapods-core (= 1.10.0)
       cocoapods-deintegrate (>= 1.0.3, < 2.0)
-      cocoapods-downloader (>= 1.2.2, < 2.0)
+      cocoapods-downloader (>= 1.4.0, < 2.0)
       cocoapods-plugins (>= 1.0.0, < 2.0)
       cocoapods-search (>= 1.0.0, < 2.0)
-      cocoapods-stats (>= 1.0.0, < 2.0)
       cocoapods-trunk (>= 1.4.0, < 2.0)
       cocoapods-try (>= 1.1.0, < 2.0)
       colored2 (~> 3.1)
@@ -49,21 +48,22 @@ GEM
       molinillo (~> 0.6.6)
       nap (~> 1.0)
       ruby-macho (~> 1.4)
-      xcodeproj (>= 1.14.0, < 2.0)
-    cocoapods-core (1.9.3)
-      activesupport (>= 4.0.2, < 6)
+      xcodeproj (>= 1.19.0, < 2.0)
+    cocoapods-core (1.10.0)
+      activesupport (> 5.0, < 6)
+      addressable (~> 2.6)
       algoliasearch (~> 1.0)
       concurrent-ruby (~> 1.1)
       fuzzy_match (~> 2.0.4)
       nap (~> 1.0)
       netrc (~> 0.11)
+      public_suffix
       typhoeus (~> 1.0)
     cocoapods-deintegrate (1.0.4)
     cocoapods-downloader (1.4.0)
     cocoapods-plugins (1.0.0)
       nap
     cocoapods-search (1.0.0)
-    cocoapods-stats (1.1.0)
     cocoapods-trunk (1.5.0)
       nap (>= 0.8, < 2.0)
       netrc (~> 0.11)
@@ -80,20 +80,21 @@ GEM
     domain_name (0.5.20190701)
       unf (>= 0.0.5, < 1.0.0)
     dotenv (2.7.6)
-    emoji_regex (3.0.0)
+    emoji_regex (3.2.0)
     escape (0.0.4)
     ethon (0.12.0)
       ffi (>= 1.3.0)
-    excon (0.76.0)
-    faraday (1.0.1)
+    excon (0.78.0)
+    faraday (1.1.0)
       multipart-post (>= 1.2, < 3)
+      ruby2_keywords
     faraday-cookie_jar (0.0.7)
       faraday (>= 0.8.0)
       http-cookie (~> 1.0.0)
     faraday_middleware (1.0.0)
       faraday (~> 1.0)
     fastimage (2.2.0)
-    fastlane (2.160.0)
+    fastlane (2.165.0)
       CFPropertyList (>= 2.3, < 4.0.0)
       addressable (>= 2.3, < 3.0.0)
       aws-sdk-s3 (~> 1.0)
@@ -144,17 +145,17 @@ GEM
     google-cloud-core (1.5.0)
       google-cloud-env (~> 1.0)
       google-cloud-errors (~> 1.0)
-    google-cloud-env (1.3.3)
+    google-cloud-env (1.4.0)
       faraday (>= 0.17.3, < 2.0)
     google-cloud-errors (1.0.1)
-    google-cloud-storage (1.28.0)
+    google-cloud-storage (1.29.1)
       addressable (~> 2.5)
       digest-crc (~> 0.4)
       google-api-client (~> 0.33)
       google-cloud-core (~> 1.2)
       googleauth (~> 0.9)
       mini_mime (~> 1.0)
-    googleauth (0.13.1)
+    googleauth (0.14.0)
       faraday (>= 0.17.3, < 2.0)
       jwt (>= 1.4, < 3.0)
       memoist (~> 0.16)
@@ -165,7 +166,7 @@ GEM
     http-cookie (1.0.3)
       domain_name (~> 0.5)
     httpclient (2.8.3)
-    i18n (0.9.5)
+    i18n (1.8.5)
       concurrent-ruby (~> 1.0)
     jazzy (0.13.5)
       cocoapods (~> 1.5)
@@ -205,6 +206,7 @@ GEM
     retriable (3.1.2)
     rouge (2.0.7)
     ruby-macho (1.4.0)
+    ruby2_keywords (0.0.2)
     rubyzip (2.3.0)
     sassc (2.4.0)
       ffi (~> 1.9)
@@ -239,10 +241,10 @@ GEM
     word_wrap (1.0.0)
     xcinvoke (0.3.0)
       liferaft (~> 0.0.6)
-    xcode-install (2.6.6)
+    xcode-install (2.6.7)
       claide (>= 0.9.1, < 1.1.0)
       fastlane (>= 2.1.0, < 3.0.0)
-    xcodeproj (1.18.0)
+    xcodeproj (1.19.0)
       CFPropertyList (>= 2.3.3, < 4.0)
       atomos (~> 0.1.3)
       claide (>= 1.0.2, < 2.0)

+ 1 - 1
Kingfisher.podspec

@@ -1,7 +1,7 @@
 Pod::Spec.new do |s|
 
   s.name         = "Kingfisher"
-  s.version      = "5.15.5"
+  s.version      = "5.15.7"
   s.summary      = "A lightweight and pure Swift implemented library for downloading and cacheing image from the web."
 
   s.description  = <<-DESC

+ 8 - 8
Kingfisher.xcodeproj/project.pbxproj

@@ -1070,9 +1070,9 @@
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
-				CURRENT_PROJECT_VERSION = 2056;
+				CURRENT_PROJECT_VERSION = 2070;
 				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 2056;
+				DYLIB_CURRENT_VERSION = 2070;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
 				GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
@@ -1119,9 +1119,9 @@
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
-				CURRENT_PROJECT_VERSION = 2056;
+				CURRENT_PROJECT_VERSION = 2070;
 				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 2056;
+				DYLIB_CURRENT_VERSION = 2070;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
 				GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
@@ -1203,9 +1203,9 @@
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
-				CURRENT_PROJECT_VERSION = 2056;
+				CURRENT_PROJECT_VERSION = 2070;
 				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 2056;
+				DYLIB_CURRENT_VERSION = 2070;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
 				GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
@@ -1258,9 +1258,9 @@
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
-				CURRENT_PROJECT_VERSION = 2056;
+				CURRENT_PROJECT_VERSION = 2070;
 				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 2056;
+				DYLIB_CURRENT_VERSION = 2070;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
 				GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;

+ 1 - 1
README.md

@@ -69,7 +69,7 @@ With the powerful options, you can do hard tasks with Kingfisher in a simple way
 ```swift
 let url = URL(string: "https://example.com/high_resolution_image.png")
 let processor = DownsamplingImageProcessor(size: imageView.bounds.size)
-             >> RoundCornerImageProcessor(cornerRadius: 20)
+             |> RoundCornerImageProcessor(cornerRadius: 20)
 imageView.kf.indicatorType = .activity
 imageView.kf.setImage(
     with: url,

+ 2 - 2
Sources/Info.plist

@@ -15,11 +15,11 @@
 	<key>CFBundlePackageType</key>
 	<string>FMWK</string>
 	<key>CFBundleShortVersionString</key>
-	<string>5.15.5</string>
+	<string>5.15.7</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>2056</string>
+	<string>2070</string>
 	<key>NSPrincipalClass</key>
 	<string></string>
 </dict>

+ 1 - 1
Sources/Networking/ImageDownloader.swift

@@ -186,7 +186,7 @@ open class ImageDownloader {
             }
         }
         sessionDelegate.onDidDownloadData.delegate(on: self) { (self, task) in
-            guard let url = task.task.originalRequest?.url else {
+            guard let url = task.originalURL else {
                 return task.mutableData
             }
             return (self.delegate ?? self).imageDownloader(self, didDownload: task.mutableData, for: url)

+ 5 - 0
Sources/Networking/SessionDataTask.swift

@@ -41,6 +41,10 @@ public class SessionDataTask {
     /// Downloaded raw data of current task.
     public private(set) var mutableData: Data
 
+    // This is a copy of `task.originalRequest?.url`. It is for getting a race-safe behavior for a pitfall on iOS 13.
+    // Ref: https://github.com/onevcat/Kingfisher/issues/1511
+    let originalURL: URL?
+
     /// The underlying download task. It is only for debugging purpose when you encountered an error. You should not
     /// modify the content of this task or start it yourself.
     public let task: URLSessionDataTask
@@ -70,6 +74,7 @@ public class SessionDataTask {
 
     init(task: URLSessionDataTask) {
         self.task = task
+        self.originalURL = task.originalRequest?.url
         mutableData = Data()
     }
 

+ 18 - 11
Sources/Networking/SessionDelegate.swift

@@ -73,8 +73,9 @@ class SessionDelegate: NSObject {
             // No other callbacks waiting, we can clear the task now.
             if !task.containsCallbacks {
                 let dataTask = task.task
-                dataTask.cancel()
-                self.remove(dataTask)
+
+                self.cancelTask(dataTask)
+                self.remove(task)
             }
         }
         let token = task.addCallback(callback)
@@ -82,6 +83,12 @@ class SessionDelegate: NSObject {
         return DownloadTask(sessionTask: task, cancelToken: token)
     }
 
+    private func cancelTask(_ dataTask: URLSessionDataTask) {
+        lock.lock()
+        defer { lock.unlock() }
+        dataTask.cancel()
+    }
+
     func append(
         _ task: SessionDataTask,
         url: URL,
@@ -91,23 +98,23 @@ class SessionDelegate: NSObject {
         return DownloadTask(sessionTask: task, cancelToken: token)
     }
 
-    private func remove(_ task: URLSessionTask) {
-        guard let url = task.originalRequest?.url else {
+    private func remove(_ task: SessionDataTask) {
+        lock.lock()
+        defer { lock.unlock() }
+
+        guard let url = task.originalURL else {
             return
         }
-        lock.lock()
-        defer {lock.unlock()}
         tasks[url] = nil
     }
 
     private func task(for task: URLSessionTask) -> SessionDataTask? {
+        lock.lock()
+        defer { lock.unlock() }
 
         guard let url = task.originalRequest?.url else {
             return nil
         }
-
-        lock.lock()
-        defer { lock.unlock() }
         guard let sessionTask = tasks[url] else {
             return nil
         }
@@ -182,7 +189,7 @@ extension SessionDelegate: URLSessionDataDelegate {
     func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
         guard let sessionTask = self.task(for: task) else { return }
 
-        if let url = task.originalRequest?.url {
+        if let url = sessionTask.originalURL {
             let result: Result<URLResponse, KingfisherError>
             if let error = error {
                 result = .failure(KingfisherError.responseError(reason: .URLSessionError(error: error)))
@@ -249,7 +256,7 @@ extension SessionDelegate: URLSessionDataDelegate {
         guard let sessionTask = self.task(for: task) else {
             return
         }
-        remove(task)
+        remove(sessionTask)
         sessionTask.onTaskDone.call((result, sessionTask.callbacks))
     }
 }

+ 13 - 9
Sources/SwiftUI/ImageBinder.swift

@@ -43,7 +43,7 @@ extension KFImage {
 
         var downloadTask: DownloadTask?
 
-        var loadingOrSuccessed: Bool = false
+        var loadingOrSucceeded: Bool = false
 
         let onFailureDelegate = Delegate<KingfisherError, Void>()
         let onSuccessDelegate = Delegate<RetrieveImageResult, Void>()
@@ -55,19 +55,21 @@ extension KFImage {
 
         init(source: Source?, options: KingfisherOptionsInfo?, isLoaded: Binding<Bool>) {
             self.source = source
-            self.options = options
+            // The refreshing of `KFImage` would happen much more frequently then an `UIImageView`, even as a
+            // "side-effect". To prevent unintended flickering, add `.loadDiskFileSynchronously` as a default.
+            self.options = (options ?? []) + [.loadDiskFileSynchronously]
             self.isLoaded = isLoaded
             self.image = nil
         }
 
         func start() {
 
-            guard !loadingOrSuccessed else { return }
+            guard !loadingOrSucceeded else { return }
 
-            loadingOrSuccessed = true
+            loadingOrSucceeded = true
 
             guard let source = source else {
-                DispatchQueue.main.async {
+                CallbackQueue.mainCurrentOrAsync.execute {
                     self.onFailureDelegate.call(KingfisherError.imageSettingError(reason: .emptySource))
                 }
                 return
@@ -93,14 +95,16 @@ extension KFImage {
                             // a `UIImage`)
                             // https://github.com/onevcat/Kingfisher/issues/1395
                             let image = value.image.kf.normalized
-                            DispatchQueue.main.async {
-                                self.image = image
+                            CallbackQueue.mainAsync.execute {
                                 self.isLoaded.wrappedValue = true
+                            }
+                            CallbackQueue.mainCurrentOrAsync.execute {
+                                self.image = image
                                 self.onSuccessDelegate.call(value)
                             }
                         case .failure(let error):
-                            self.loadingOrSuccessed = false
-                            DispatchQueue.main.async {
+                            self.loadingOrSucceeded = false
+                            CallbackQueue.mainCurrentOrAsync.execute {
                                 self.onFailureDelegate.call(error)
                             }
                         }

+ 1 - 1
Sources/SwiftUI/KFImage.swift

@@ -105,7 +105,7 @@ public struct KFImage: SwiftUI.View {
                     guard let binder = binder else {
                         return
                     }
-                    if !binder.loadingOrSuccessed {
+                    if !binder.loadingOrSucceeded {
                         binder.start()
                     }
                 }

+ 2 - 2
Tests/KingfisherTests/Info.plist

@@ -15,10 +15,10 @@
 	<key>CFBundlePackageType</key>
 	<string>BNDL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>5.15.5</string>
+	<string>5.15.7</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>2056</string>
+	<string>2070</string>
 </dict>
 </plist>

+ 4 - 2
Tests/KingfisherTests/RetryStrategyTests.swift

@@ -146,11 +146,13 @@ class RetryStrategyTests: XCTestCase {
         var blockCalled: [Bool] = []
         let source = Source.network(URL(string: "url")!)
         let retry = DelayRetryStrategy(maxRetryCount: 3, retryInterval: .seconds(0))
+
+        let task = URLSession.shared.dataTask(with: URL(string: "url")!)
+
         let context1 = RetryContext(
             source: source,
-            error: .requestError(reason: .taskCancelled(task: .init(task: .init()), token: .init()))
+            error: .requestError(reason: .taskCancelled(task: .init(task: task), token: .init()))
         )
-
         retry.retry(context: context1) { decision in
             guard case RetryDecision.stop = decision else {
                 XCTFail("The decision should be `stop` if user cancelled the task.")