Browse Source

Refactor for downloader first step by using delegate object

onevcat 7 years ago
parent
commit
a6779bdaf6

+ 10 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -117,6 +117,10 @@
 		D13646752165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
 		D13646762165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
 		D13646772165A1A100A33652 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13646732165A1A100A33652 /* Result.swift */; };
+		D1839845216E333E003927D3 /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1839844216E333E003927D3 /* Delegate.swift */; };
+		D1839846216E333E003927D3 /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1839844216E333E003927D3 /* Delegate.swift */; };
+		D1839847216E333E003927D3 /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1839844216E333E003927D3 /* Delegate.swift */; };
+		D1839848216E333E003927D3 /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1839844216E333E003927D3 /* Delegate.swift */; };
 		D1A37BC6215D2DBA009B39B7 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB6B6215D2BB50013BA68 /* ImageCache.swift */; };
 		D1A37BC7215D2DBA009B39B7 /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB6B7215D2BB50013BA68 /* CacheSerializer.swift */; };
 		D1A37BC8215D2DBA009B39B7 /* FormatIndicatedCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12AB6B8215D2BB50013BA68 /* FormatIndicatedCacheSerializer.swift */; };
@@ -261,6 +265,7 @@
 		D13646732165A1A100A33652 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
 		D13F49D61BEDA67C00CE335D /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		D16799EB1C4E74460020FD12 /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		D1839844216E333E003927D3 /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; };
 		D1A37BDD215D34E8009B39B7 /* ImageDrawing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDrawing.swift; sourceTree = "<group>"; };
 		D1A37BE2215D359F009B39B7 /* ImageFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFormat.swift; sourceTree = "<group>"; };
 		D1A37BE7215D365A009B39B7 /* ExtenionHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtenionHelpers.swift; sourceTree = "<group>"; };
@@ -452,6 +457,7 @@
 				D12AB6BC215D2BB50013BA68 /* String+MD5.swift */,
 				D1A37BE7215D365A009B39B7 /* ExtenionHelpers.swift */,
 				D1A37BF1215D3850009B39B7 /* SizeExtensions.swift */,
+				D1839844216E333E003927D3 /* Delegate.swift */,
 			);
 			path = Utility;
 			sourceTree = "<group>";
@@ -945,6 +951,7 @@
 				D13646762165A1A100A33652 /* Result.swift in Sources */,
 				D1A37BCA215D2DBA009B39B7 /* NSButton+Kingfisher.swift in Sources */,
 				D1A37BCB215D2DBA009B39B7 /* Kingfisher.swift in Sources */,
+				D1839847216E333E003927D3 /* Delegate.swift in Sources */,
 				D1A37BCC215D2DBA009B39B7 /* KingfisherError.swift in Sources */,
 				D1A37BCD215D2DBA009B39B7 /* KingfisherManager.swift in Sources */,
 				D1A37BE5215D359F009B39B7 /* ImageFormat.swift in Sources */,
@@ -1013,6 +1020,7 @@
 				D12AB719215D2BB50013BA68 /* CacheSerializer.swift in Sources */,
 				D12AB731215D2BB50013BA68 /* AnimatedImageView.swift in Sources */,
 				D12AB6E5215D2BB50013BA68 /* Placeholder.swift in Sources */,
+				D1839846216E333E003927D3 /* Delegate.swift in Sources */,
 				D12AB6D9215D2BB50013BA68 /* ImageTransition.swift in Sources */,
 				D1A37BE9215D365A009B39B7 /* ExtenionHelpers.swift in Sources */,
 				D12AB721215D2BB50013BA68 /* ThreadHelper.swift in Sources */,
@@ -1059,6 +1067,7 @@
 				D12AB707215D2BB50013BA68 /* Kingfisher.swift in Sources */,
 				D12AB71F215D2BB50013BA68 /* FormatIndicatedCacheSerializer.swift in Sources */,
 				D12AB703215D2BB50013BA68 /* WKInterfaceImage+Kingfisher.swift in Sources */,
+				D1839848216E333E003927D3 /* Delegate.swift in Sources */,
 				D12AB70F215D2BB50013BA68 /* KingfisherManager.swift in Sources */,
 				D1A37BF0215D375F009B39B7 /* Deprecated.swift in Sources */,
 				D13646772165A1A100A33652 /* Result.swift in Sources */,
@@ -1084,6 +1093,7 @@
 				D12AB718215D2BB50013BA68 /* CacheSerializer.swift in Sources */,
 				D12AB730215D2BB50013BA68 /* AnimatedImageView.swift in Sources */,
 				D12AB6E4215D2BB50013BA68 /* Placeholder.swift in Sources */,
+				D1839845216E333E003927D3 /* Delegate.swift in Sources */,
 				D12AB6D8215D2BB50013BA68 /* ImageTransition.swift in Sources */,
 				D1A37BE8215D365A009B39B7 /* ExtenionHelpers.swift in Sources */,
 				D12AB720215D2BB50013BA68 /* ThreadHelper.swift in Sources */,

+ 3 - 0
Sources/General/Deprecated.swift

@@ -65,6 +65,9 @@ extension KingfisherClass where Base: Image {
 @available(*, deprecated, message: "Use `Result<ImageResult>` based callback instead")
 public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> Void)
 
+@available(*, deprecated, message: "Use `Result<ImageDownloadResult>` based callback instead")
+public typealias ImageDownloaderCompletionHandler = ((_ image: Image?, _ error: NSError?, _ url: URL?, _ originalData: Data?) -> Void)
+
 extension RetrieveImageTask {
     @available(*, deprecated, message: "RetrieveImageTask.empty will be removed soon. Use `nil` to represnt a no task.")
     public static let empty = RetrieveImageTask()

+ 20 - 7
Sources/Image/ImageProcessor.swift

@@ -70,12 +70,11 @@ public protocol ImageProcessor {
     ///         If input item is already an image and there is any errors in processing, the input 
     ///         image itself will be returned.
     /// - Note: Most processor only supports CG-based images. 
-    ///         watchOS is not supported for processors containing filter, the input image will be returned directly on watchOS.
+    ///         watchOS is not supported for processors containing filter, the input image will be
+    ///         returned directly on watchOS.
     func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image?
 }
 
-typealias ProcessorImp = ((ImageProcessItem, KingfisherOptionsInfo) -> Image?)
-
 public extension ImageProcessor {
     
     /// Append an `ImageProcessor` to another. The identifier of the new `ImageProcessor` 
@@ -106,7 +105,8 @@ func !=(left: ImageProcessor, right: ImageProcessor) -> Bool {
     return !(left == right)
 }
 
-fileprivate struct GeneralProcessor: ImageProcessor {
+typealias ProcessorImp = ((ImageProcessItem, KingfisherOptionsInfo) -> Image?)
+struct GeneralProcessor: ImageProcessor {
     let identifier: String
     let p: ProcessorImp
     func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? {
@@ -177,6 +177,7 @@ public struct BlendImageProcessor: ImageProcessor {
 
     /// Blend Mode will be used to blend the input image.
     public let blendMode: CGBlendMode
+
     /// Alpha will be used when blend image.
     public let alpha: CGFloat
 
@@ -271,7 +272,10 @@ public struct CompositingImageProcessor: ImageProcessor {
         switch item {
         case .image(let image):
             return image.kf.scaled(to: options.scaleFactor)
-                        .kf.image(withCompositingOperation: compositingOperation, alpha: alpha, backgroundColor: backgroundColor)
+                        .kf.image(
+                            withCompositingOperation: compositingOperation,
+                            alpha: alpha,
+                            backgroundColor: backgroundColor)
         case .data:
             return (DefaultImageProcessor.default >> self).process(item: item, options: options)
         }
@@ -307,7 +311,12 @@ public struct RoundCornerImageProcessor: ImageProcessor {
     ///                              Default is `nil`.
     /// - parameter corners:         The target corners which will be applied rounding. Default is `.all`.
     /// - parameter backgroundColor: Background color to apply for the output image. Default is `nil`.
-    public init(cornerRadius: CGFloat, targetSize: CGSize? = nil, roundingCorners corners: RectCorner = .all, backgroundColor: Color? = nil) {
+    public init(
+        cornerRadius: CGFloat,
+        targetSize: CGSize? = nil,
+        roundingCorners corners: RectCorner = .all,
+        backgroundColor: Color? = nil)
+    {
         self.cornerRadius = cornerRadius
         self.targetSize = targetSize
         self.roundingCorners = corners
@@ -342,7 +351,11 @@ public struct RoundCornerImageProcessor: ImageProcessor {
         case .image(let image):
             let size = targetSize ?? image.kf.size
             return image.kf.scaled(to: options.scaleFactor)
-                        .kf.image(withRoundRadius: cornerRadius, fit: size, roundingCorners: roundingCorners, backgroundColor: backgroundColor)
+                        .kf.image(
+                            withRoundRadius: cornerRadius,
+                            fit: size,
+                            roundingCorners: roundingCorners,
+                            backgroundColor: backgroundColor)
         case .data:
             return (DefaultImageProcessor.default >> self).process(item: item, options: options)
         }

+ 277 - 155
Sources/Networking/ImageDownloader.swift

@@ -33,8 +33,13 @@ import UIKit
 /// Progress update block of downloader.
 public typealias ImageDownloaderProgressBlock = DownloadProgressBlock
 
-/// Completion block of downloader.
-public typealias ImageDownloaderCompletionHandler = ((_ image: Image?, _ error: NSError?, _ url: URL?, _ originalData: Data?) -> Void)
+public typealias ResultImageDownloaderCompletionHandler = ((Result<ImageDownloadResult>) -> Void)
+
+public struct ImageDownloadResult {
+    public let image: Image
+    public let url: URL
+    public let originalData: Data
+}
 
 /// Download task.
 public struct RetrieveImageDownloadTask {
@@ -42,7 +47,6 @@ public struct RetrieveImageDownloadTask {
     
     /// Downloader by which this task is initialized.
     public private(set) weak var ownerDownloader: ImageDownloader?
-
     
     /// Cancel this download task. It will trigger the completion handler with an NSURLErrorCancelled error.
     /// If you want to cancel all downloading tasks, call `cancelAll()` of `ImageDownloader` instance.
@@ -98,73 +102,83 @@ public let KingfisherErrorStatusCodeKey = "statusCode"
 
 /// Protocol of `ImageDownloader`.
 public protocol ImageDownloaderDelegate: AnyObject {
-    /**
-     Called when the `ImageDownloader` object will start downloading an image from specified URL.
-     
-     - parameter downloader: The `ImageDownloader` object finishes the downloading.
-     - parameter url:        URL of the original request URL.
-     - parameter response:   The request object for the download process.
-     */
+
+    /// Called when the `ImageDownloader` object will start downloading an image from specified URL.
+    ///
+    /// - Parameters:
+    ///   - downloader: The `ImageDownloader` object finishes the downloading.
+    ///   - url: URL of the original request URL.
+    ///   - request: The request object for the download process.
     func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?)
-    
-    /**
-     Called when the `ImageDownloader` completes a downloading request with success or failure.
-     
-     - parameter downloader: The `ImageDownloader` object finishes the downloading.
-     - parameter url:        URL of the original request URL.
-     - parameter response:   The response object of the downloading process.
-     - parameter error:      The error in case of failure.
-     */
-    func imageDownloader(_ downloader: ImageDownloader, didFinishDownloadingImageForURL url: URL, with response: URLResponse?, error: Error?)
-    
-    /**
-    Called when the `ImageDownloader` object successfully downloaded an image from specified URL.
-    
-    - parameter downloader: The `ImageDownloader` object finishes the downloading.
-    - parameter image:      Downloaded image.
-    - parameter url:        URL of the original request URL.
-    - parameter response:   The response object of the downloading process.
-    */
-    func imageDownloader(_ downloader: ImageDownloader, didDownload image: Image, for url: URL, with response: URLResponse?)
-    
-    /**
-    Check if a received HTTP status code is valid or not. 
-    By default, a status code between 200 to 400 (excluded) is considered as valid.
-    If an invalid code is received, the downloader will raise an .invalidStatusCode error.
-    It has a `userInfo` which includes this statusCode and localizedString error message.
-     
-    - parameter code: The received HTTP status code.
-    - parameter downloader: The `ImageDownloader` object asking for validate status code.
-     
-    - returns: Whether this HTTP status code is valid or not.
-     
-    - Note: If the default 200 to 400 valid code does not suit your need, 
-            you can implement this method to change that behavior.
-    */
+
+    /// Called when the `ImageDownloader` completes a downloading request with success or failure.
+    ///
+    /// - Parameters:
+    ///   - downloader: The `ImageDownloader` object finishes the downloading.
+    ///   - url: URL of the original request URL.
+    ///   - response: The response object of the downloading process.
+    ///   - error: The error in case of failure.
+    func imageDownloader(
+        _ downloader: ImageDownloader,
+        didFinishDownloadingImageForURL url: URL,
+        with response: URLResponse?, error: Error?)
+
+    /// Called when the `ImageDownloader` object successfully downloaded an image from specified URL.
+    ///
+    /// - Parameters:
+    ///   - downloader: The `ImageDownloader` object finishes the downloading.
+    ///   - image: Downloaded image.
+    ///   - url: URL of the original request URL.
+    ///   - response: The response object of the downloading process.
+    func imageDownloader(
+        _ downloader: ImageDownloader,
+        didDownload image: Image,
+        for url: URL,
+        with response: URLResponse?)
+
+    /// Check if a received HTTP status code is valid or not.
+    /// By default, a status code between 200 to 400 (excluded) is considered as valid.
+    /// If an invalid code is received, the downloader will raise an .invalidStatusCode error.
+    /// It has a `userInfo` which includes this statusCode and localizedString error message.
+    ///
+    /// - Parameters:
+    ///   - code: The received HTTP status code.
+    ///   - downloader: The `ImageDownloader` object asking for validate status code.
+    /// - Returns: Whether this HTTP status code is valid or not.
+    /// - Note: If the default 200 to 400 valid code does not suit your need,
+    ///         you can implement this method to change that behavior.
     func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool
-    
-    /**
-     Called when the `ImageDownloader` object successfully downloaded image data from specified URL.
-     
-     - parameter downloader: The `ImageDownloader` object finishes data downloading.
-     - parameter data:       Downloaded data.
-     - parameter url:        URL of the original request URL.
-     
-     - returns: The data from which Kingfisher should use to create an image.
-     
-     - Note: This callback can be used to preprocess raw image data
-             before creation of UIImage instance (i.e. decrypting or verification).
-     */
+
+    /// Called when the `ImageDownloader` object successfully downloaded image data from specified URL.
+    ///
+    /// - Parameters:
+    ///   - downloader: The `ImageDownloader` object finishes data downloading.
+    ///   - data: Downloaded data.
+    ///   - url: URL of the original request URL.
+    /// - Returns: The data from which Kingfisher should use to create an image.
+    /// - Note: This callback can be used to preprocess raw image data before
+    ///         creation of UIImage instance (i.e. decrypting or verification).
     func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data?
 }
 
 extension ImageDownloaderDelegate {
     
-    public func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?) {}
+    public func imageDownloader(
+        _ downloader: ImageDownloader,
+        willDownloadImageForURL url: URL,
+        with request: URLRequest?) {}
     
-    public func imageDownloader(_ downloader: ImageDownloader, didFinishDownloadingImageForURL url: URL, with response: URLResponse?, error: Error?) {}
+    public func imageDownloader(
+        _ downloader: ImageDownloader,
+        didFinishDownloadingImageForURL url: URL,
+        with response: URLResponse?,
+        error: Error?) {}
     
-    public func imageDownloader(_ downloader: ImageDownloader, didDownload image: Image, for url: URL, with response: URLResponse?) {}
+    public func imageDownloader(
+        _ downloader: ImageDownloader,
+        didDownload image: Image,
+        for url: URL,
+        with response: URLResponse?) {}
     
     public func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool {
         return (200..<400).contains(code)
@@ -176,36 +190,44 @@ extension ImageDownloaderDelegate {
 
 /// Protocol indicates that an authentication challenge could be handled.
 public protocol AuthenticationChallengeResponsable: AnyObject {
-    /**
-     Called when an session level authentication challenge is received.
-     This method provide a chance to handle and response to the authentication challenge before downloading could start.
-     
-     - parameter downloader:        The downloader which receives this challenge.
-     - parameter challenge:         An object that contains the request for authentication.
-     - parameter completionHandler: A handler that your delegate method must call.
-     
-     - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`. Please refer to the document of it in `URLSessionDelegate`.
-     */
-    func downloader(_ downloader: ImageDownloader, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
 
-    /**
-     Called when an session level authentication challenge is received.
-     This method provide a chance to handle and response to the authentication challenge before downloading could start.
-     
-     - parameter downloader:        The downloader which receives this challenge.
-     - parameter task:              The task whose request requires authentication.
-     - parameter challenge:         An object that contains the request for authentication.
-     - parameter completionHandler: A handler that your delegate method must call.
-     
-     - Note: This method is a forward from `URLSessionTaskDelegate.urlSession(:task:didReceiveChallenge:completionHandler:)`. Please refer to the document of it in `URLSessionTaskDelegate`.
-     */
-    func downloader(_ downloader: ImageDownloader, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
+    /// Called when an session level authentication challenge is received.
+    /// This method provide a chance to handle and response to the authentication challenge before downloading could start.
+    ///
+    /// - Parameters:
+    ///   - downloader: The downloader which receives this challenge.
+    ///   - challenge: An object that contains the request for authentication.
+    ///   - completionHandler: A handler that your delegate method must call.
+    /// - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`.
+    ///         Please refer to the document of it in `URLSessionDelegate`.
+    func downloader(
+        _ downloader: ImageDownloader,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
+
+    /// Called when an session level authentication challenge is received.
+    /// This method provide a chance to handle and response to the authentication
+    /// challenge before downloading could start.
+    ///
+    /// - Parameters:
+    ///   - downloader: The downloader which receives this challenge.
+    ///   - task: The task whose request requires authentication.
+    ///   - challenge: An object that contains the request for authentication.
+    ///   - completionHandler: A handler that your delegate method must call.
+    func downloader(
+        _ downloader: ImageDownloader,
+        task: URLSessionTask,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
 }
 
 extension AuthenticationChallengeResponsable {
     
-    func downloader(_ downloader: ImageDownloader, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-    
+    func downloader(
+        _ downloader: ImageDownloader,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
+    {
         if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
             if let trustedHosts = downloader.trustedHosts, trustedHosts.contains(challenge.protectionSpace.host) {
                 let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
@@ -217,8 +239,12 @@ extension AuthenticationChallengeResponsable {
         completionHandler(.performDefaultHandling, nil)
     }
     
-    func downloader(_ downloader: ImageDownloader, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-        
+    func downloader(
+        _ downloader: ImageDownloader,
+        task: URLSessionTask,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
+    {
         completionHandler(.performDefaultHandling, nil)
     }
 
@@ -233,20 +259,25 @@ open class ImageDownloader {
 
         var downloadTaskCount = 0
         var downloadTask: RetrieveImageDownloadTask?
-        var cancelSemaphore: DispatchSemaphore?
     }
     
     // MARK: - Public property
     /// The duration before the download is timeout. Default is 15 seconds.
     open var downloadTimeout: TimeInterval = 15.0
     
-    /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this set will be ignored. 
-    /// You can use this set to specify the self-signed site. It only will be used if you don't specify the `authenticationChallengeResponder`. 
-    /// If `authenticationChallengeResponder` is set, this property will be ignored and the implementation of `authenticationChallengeResponder` will be used instead.
+    /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this
+    /// set will be ignored. You can use this set to specify the self-signed site. It only will be used if you don't
+    /// specify the `authenticationChallengeResponder`.
+    ///
+    /// If `authenticationChallengeResponder` is set, this property will be ignored and the implementation of
+    /// `authenticationChallengeResponder` will be used instead.
     open var trustedHosts: Set<String>?
     
-    /// Use this to set supply a configuration for the downloader. By default, NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used. 
-    /// You could change the configuration before a downloading task starts. A configuration without persistent storage for caches is requested for downloader working correctly.
+    /// Use this to set supply a configuration for the downloader. By default,
+    /// NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used.
+    ///
+    /// You could change the configuration before a downloading task starts.
+    /// A configuration without persistent storage for caches is requested for downloader working correctly.
     open var sessionConfiguration = URLSessionConfiguration.ephemeral {
         didSet {
             session?.invalidateAndCancel()
@@ -268,7 +299,6 @@ open class ImageDownloader {
     open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable?
     
     // MARK: - Internal property
-    let barrierQueue: DispatchQueue
     let processQueue: DispatchQueue
     let cancelQueue: DispatchQueue
     
@@ -290,11 +320,10 @@ open class ImageDownloader {
             fatalError("[Kingfisher] You should specify a name for the downloader. A downloader with empty name is not permitted.")
         }
         
-        barrierQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Barrier.\(name)", attributes: .concurrent)
         processQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process.\(name)", attributes: .concurrent)
         cancelQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Cancel.\(name)")
         
-        sessionHandler = ImageDownloaderSessionHandler(name: name)
+        sessionHandler = ImageDownloaderSessionHandler()
 
         // Provide a default implement for challenge responder.
         authenticationChallengeResponder = sessionHandler
@@ -307,7 +336,7 @@ open class ImageDownloader {
     
     func fetchLoad(for url: URL) -> ImageFetchLoad? {
         var fetchLoad: ImageFetchLoad?
-        barrierQueue.sync(flags: .barrier) { fetchLoad = fetchLoads[url] }
+        fetchLoad = fetchLoads[url]
         return fetchLoad
     }
     
@@ -386,32 +415,19 @@ extension ImageDownloader {
     func setup(progressBlock: ImageDownloaderProgressBlock?, with completionHandler: ImageDownloaderCompletionHandler?, for url: URL, options: KingfisherOptionsInfo?, started: @escaping ((URLSession, ImageFetchLoad) -> Void)) {
 
         func prepareFetchLoad() {
-            barrierQueue.sync(flags: .barrier) {
-                let loadObjectForURL = fetchLoads[url] ?? ImageFetchLoad()
-                let callbackPair = (progressBlock: progressBlock, completionHandler: completionHandler)
-                
-                loadObjectForURL.contents.append((callbackPair, options ?? .empty))
-                
-                fetchLoads[url] = loadObjectForURL
-                
-                if let session = session {
-                    started(session, loadObjectForURL)
-                }
-            }
-        }
-        
-        if let fetchLoad = fetchLoad(for: url), fetchLoad.downloadTaskCount == 0 {
-            if fetchLoad.cancelSemaphore == nil {
-                fetchLoad.cancelSemaphore = DispatchSemaphore(value: 0)
-            }
-            cancelQueue.async {
-                _ = fetchLoad.cancelSemaphore?.wait(timeout: .distantFuture)
-                fetchLoad.cancelSemaphore = nil
-                prepareFetchLoad()
+            let loadObjectForURL = fetchLoads[url] ?? ImageFetchLoad()
+            let callbackPair = (progressBlock: progressBlock, completionHandler: completionHandler)
+
+            loadObjectForURL.contents.append((callbackPair, options ?? .empty))
+
+            fetchLoads[url] = loadObjectForURL
+
+            if let session = session {
+                started(session, loadObjectForURL)
             }
-        } else {
-            prepareFetchLoad()
         }
+
+        prepareFetchLoad()
     }
     
     private func cancelTaskImpl(_ task: RetrieveImageDownloadTask, fetchLoad: ImageFetchLoad? = nil, ignoreTaskCount: Bool = false) {
@@ -436,7 +452,7 @@ extension ImageDownloader {
     }
     
     func cancel(_ task: RetrieveImageDownloadTask) {
-        barrierQueue.sync(flags: .barrier) { cancelTaskImpl(task) }
+        cancelTaskImpl(task)
     }
     
     /// Cancel all downloading tasks. It will trigger the completion handlers for all not-yet-finished
@@ -445,16 +461,15 @@ extension ImageDownloader {
     /// If you need to only cancel a certain task, call `cancel()` on the `RetrieveImageDownloadTask`
     /// returned by the downloading methods.
     public func cancelAll() {
-        barrierQueue.sync(flags: .barrier) {
-            fetchLoads.forEach { v in
-                let fetchLoad = v.value
-                guard let task = fetchLoad.downloadTask else { return }
-                cancelTaskImpl(task, fetchLoad: fetchLoad, ignoreTaskCount: true)
-            }
+        fetchLoads.forEach { v in
+            let fetchLoad = v.value
+            guard let task = fetchLoad.downloadTask else { return }
+            cancelTaskImpl(task, fetchLoad: fetchLoad, ignoreTaskCount: true)
         }
     }
 }
 
+
 // MARK: - NSURLSessionDataDelegate
 
 /// Delegate class for `NSURLSessionTaskDelegate`.
@@ -464,24 +479,9 @@ extension ImageDownloader {
 // See https://github.com/onevcat/Kingfisher/issues/235
 final class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, AuthenticationChallengeResponsable {
 
-    private let downloaderQueue: DispatchQueue
-
     // The holder will keep downloader not released while a data task is being executed.
     // It will be set when the task started, and reset when the task finished.
-    private var _downloadHolder: ImageDownloader?
-    var downloadHolder: ImageDownloader? {
-        get {
-            return downloaderQueue.sync { _downloadHolder }
-        }
-        set {
-            downloaderQueue.sync { _downloadHolder = newValue }
-        }
-    }
-
-    init(name: String) {
-        downloaderQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.SessionHandler.\(name)")
-        super.init()
-    }
+    var downloadHolder: ImageDownloader?
     
     func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
         
@@ -573,11 +573,9 @@ final class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Aut
             return
         }
 
-        downloader.barrierQueue.sync(flags: .barrier) {
-            downloader.fetchLoads.removeValue(forKey: url)
-            if downloader.fetchLoads.isEmpty {
-                downloadHolder = nil
-            }
+        downloader.fetchLoads.removeValue(forKey: url)
+        if downloader.fetchLoads.isEmpty {
+            downloadHolder = nil
         }
     }
     
@@ -589,11 +587,6 @@ final class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Aut
         // We need to clean the fetch load first, before actually calling completion handler.
         cleanFetchLoad(for: url)
         
-        var leftSignal: Int
-        repeat {
-            leftSignal = fetchLoad.cancelSemaphore?.signal() ?? 0
-        } while leftSignal != 0
-        
         for content in fetchLoad.contents {
             content.options.callbackDispatchQueue.safeAsync {
                 content.callback.completionHandler?(nil, error as NSError, url, nil)
@@ -675,3 +668,132 @@ final class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Aut
 // Placeholder. For retrieving extension methods of ImageDownloaderDelegate
 extension ImageDownloader: ImageDownloaderDelegate {}
 
+class SessionDelegate: NSObject, URLSessionDataDelegate {
+    private var tasks: [URL: SessionDataTask] = [:]
+    private let lock = NSLock()
+    
+    let session: URLSession
+    
+    init(session: URLSession) {
+        self.session = session
+    }
+    
+    func add(
+        _ requst: URLRequest,
+        onProgress: Delegate<(Int64, Int64), Void>?,
+        onCompleted: Delegate<Result<Data>, Void>?)
+    {
+        guard let url = requst.url else {
+            return
+        }
+        
+        lock.lock()
+        defer { lock.unlock() }
+        
+        let callback = SessionDataTask.TaskCallback(
+            onProgress: onProgress,
+            onCompleted: onCompleted)
+        
+        if let task = tasks[url] {
+            task.downloadTaskCount += 1
+            task.callbacks.append(callback)
+        } else {
+            let task = SessionDataTask(session: session, request: requst)
+            task.callbacks.append(callback)
+            tasks[url] = task
+        }
+    }
+    
+    func remove(_ task: URLSessionTask) {
+        guard let url = task.originalRequest?.url else {
+            return
+        }
+        lock.lock()
+        defer { lock.unlock() }
+        tasks.removeValue(forKey: url)
+    }
+    
+    func task(for task: URLSessionTask) -> SessionDataTask? {
+        guard let url = task.originalRequest?.url else {
+            return nil
+        }
+        return tasks[url]
+    }
+    
+    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
+        lock.lock()
+        defer { lock.unlock() }
+        
+        guard let task = self.task(for: dataTask) else {
+            return
+        }
+        task.didReceiveData(data)
+        
+        if let expectedContentLength = dataTask.response?.expectedContentLength, expectedContentLength != -1 {
+            DispatchQueue.main.async {
+                task.callbacks.forEach { callback in
+                    callback.onProgress?.call((Int64(data.count), expectedContentLength))
+                }
+            }
+        }
+    }
+    
+    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+        lock.lock()
+        defer { lock.unlock() }
+        
+        guard let url = task.originalRequest?.url else {
+            return
+        }
+        guard let dataTask = self.task(for: task) else {
+            return
+        }
+        
+        tasks.removeValue(forKey: url)
+        
+        if let error = error {
+            dataTask.callbacks.forEach { callback in
+                callback.onCompleted?.call(.failure(error))
+            }
+        } else {
+            dataTask.callbacks.forEach { callback in
+                callback.onCompleted?.call(.success(dataTask.mutableData))
+            }
+        }
+    }
+}
+
+class SessionDataTask {
+    
+    struct TaskCallback {
+        let onProgress: Delegate<(Int64, Int64), Void>?
+        let onCompleted: Delegate<Result<Data>, Void>?
+    }
+    
+    var mutableData: Data
+    let task: URLSessionDataTask
+
+    var callbacks = [TaskCallback]()
+    
+    var downloadTaskCount = 0
+    
+    init(session: URLSession, request: URLRequest) {
+        task = session.dataTask(with: request)
+        mutableData = Data()
+    }
+    
+    func resume() {
+        task.resume()
+    }
+    
+    func cancel() {
+        downloadTaskCount -= 1
+        if downloadTaskCount == 0 {
+            
+        }
+    }
+    
+    func didReceiveData(_ data: Data) {
+        mutableData.append(data)
+    }
+}

+ 53 - 0
Sources/Utility/Delegate.swift

@@ -0,0 +1,53 @@
+//
+//  Delegate.swift
+//  Kingfisher
+//
+//  Created by onevcat on 2018/10/10.
+//
+//  Copyright (c) 2018 Wei Wang <onevcat@gmail.com>
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+
+import Foundation
+
+/// A delegate helper type to "shadow" weak `self`, to prevent creating an unexpected retain cycle.
+class Delegate<Input, Output> {
+    init() {}
+    
+    private var block: ((Input) -> Output?)?
+    
+    func delegate<T: AnyObject>(on target: T, block: ((T, Input) -> Output)?) {
+        // The `target` is weak inside block, so you do not need to worry about it in the caller side.
+        self.block = { [weak target] input in
+            guard let target = target else { return nil }
+            return block?(target, input)
+        }
+    }
+    
+    func call(_ input: Input) -> Output? {
+        return block?(input)
+    }
+}
+
+extension Delegate where Input == Void {
+    // To make syntax better for `Void` input.
+    func call() -> Output? {
+        return call(())
+    }
+}