onevcat 7 лет назад
Родитель
Сommit
d655c6edd4

+ 2 - 2
Sources/Networking/AuthenticationChallengeResponsable.swift

@@ -29,7 +29,7 @@ import Foundation
 /// Protocol indicates that an authentication challenge could be handled.
 /// Protocol indicates that an authentication challenge could be handled.
 public protocol AuthenticationChallengeResponsable: AnyObject {
 public protocol AuthenticationChallengeResponsable: AnyObject {
 
 
-    /// Called when an session level authentication challenge is received.
+    /// Called when a session level authentication challenge is received.
     /// This method provide a chance to handle and response to the authentication
     /// This method provide a chance to handle and response to the authentication
     /// challenge before downloading could start.
     /// challenge before downloading could start.
     ///
     ///
@@ -45,7 +45,7 @@ public protocol AuthenticationChallengeResponsable: AnyObject {
         didReceive challenge: URLAuthenticationChallenge,
         didReceive challenge: URLAuthenticationChallenge,
         completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
         completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
 
 
-    /// Called when an session level authentication challenge is received.
+    /// Called when a task level authentication challenge is received.
     /// This method provide a chance to handle and response to the authentication
     /// This method provide a chance to handle and response to the authentication
     /// challenge before downloading could start.
     /// challenge before downloading could start.
     ///
     ///

+ 5 - 2
Sources/Networking/ImageDataProcessor.swift

@@ -26,6 +26,7 @@
 
 
 import Foundation
 import Foundation
 
 
+// Handles image processing work on an own process queue.
 class ImageDataProcessor {
 class ImageDataProcessor {
     let data: Data
     let data: Data
     let callbacks: [SessionDataTask.TaskCallback]
     let callbacks: [SessionDataTask.TaskCallback]
@@ -56,18 +57,20 @@ class ImageDataProcessor {
                 processedImages[processor.identifier] = image
                 processedImages[processor.identifier] = image
             }
             }
 
 
+            let result: Result<Image>
             if let image = image {
             if let image = image {
                 let imageModifier = callback.options.imageModifier
                 let imageModifier = callback.options.imageModifier
                 var finalImage = imageModifier.modify(image)
                 var finalImage = imageModifier.modify(image)
                 if callback.options.backgroundDecode {
                 if callback.options.backgroundDecode {
                     finalImage = finalImage.kf.decoded
                     finalImage = finalImage.kf.decoded
                 }
                 }
-                onImageProcessed.call((.success(finalImage), callback))
+                result = .success(finalImage)
             } else {
             } else {
                 let error = KingfisherError.processorError(
                 let error = KingfisherError.processorError(
                     reason: .processingFailed(processor: processor, item: .data(data)))
                     reason: .processingFailed(processor: processor, item: .data(data)))
-                onImageProcessed.call((.failure(error), callback))
+                result = .failure(error)
             }
             }
+            onImageProcessed.call((result, callback))
         }
         }
     }
     }
 }
 }

+ 10 - 5
Sources/Networking/ImageDownloader.swift

@@ -46,12 +46,17 @@ public struct ImageDownloadResult {
 /// Represents a task of an image downloading process.
 /// Represents a task of an image downloading process.
 public struct DownloadTask {
 public struct DownloadTask {
 
 
-    // Multiple `DownloadTask`s could refer to a same `sessionTask`. This is an optimization in Kingfisher to
-    // prevent multiple downloading task for the same URL resource at the same time.
-    let sessionTask: SessionDataTask
+    /// The `SessionDataTask` object bounded to this download task. Multiple `DownloadTask`s could refer
+    /// to a same `sessionTask`. This is an optimization in Kingfisher to prevent multiple downloading task
+    /// for the same URL resource at the same time.
+    ///
+    /// When you `cancel` a `DownloadTask`, this `SessionDataTask` and its cancel token will be pass through.
+    /// You can use them to identify the cancelled task.
+    public let sessionTask: SessionDataTask
 
 
-    // Callbacks for this `DownloadTask` needs to be identified with the `CancelToken`.
-    let cancelToken: SessionDataTask.CancelToken
+    /// The cancel token which is used to cancel the task. This is only for identify the task when it is cancelled.
+    /// To cancel a `DownloadTask`, use `cancel` instead.
+    public let cancelToken: SessionDataTask.CancelToken
 
 
     /// Cancel this task if it is running. It will do nothing if this task is not running.
     /// Cancel this task if it is running. It will do nothing if this task is not running.
     ///
     ///

+ 29 - 20
Sources/Networking/ImageDownloaderDelegate.swift

@@ -26,14 +26,15 @@
 
 
 import Foundation
 import Foundation
 
 
-/// Protocol of `ImageDownloader`.
+/// Protocol of `ImageDownloader`. This protocol provides a set of methods which are related to image downloader
+/// working stages and rules.
 public protocol ImageDownloaderDelegate: AnyObject {
 public protocol ImageDownloaderDelegate: AnyObject {
 
 
-    /// Called when the `ImageDownloader` object will start downloading an image from specified URL.
+    /// Called when the `ImageDownloader` object will start downloading an image from a specified URL.
     ///
     ///
     /// - Parameters:
     /// - Parameters:
     ///   - downloader: The `ImageDownloader` object which is used for the downloading operation.
     ///   - downloader: The `ImageDownloader` object which is used for the downloading operation.
-    ///   - url: URL of the original request URL.
+    ///   - url: URL of the starting request.
     ///   - request: The request object for the download process.
     ///   - request: The request object for the download process.
     ///
     ///
     func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?)
     func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?)
@@ -49,9 +50,28 @@ public protocol ImageDownloaderDelegate: AnyObject {
     func imageDownloader(
     func imageDownloader(
         _ downloader: ImageDownloader,
         _ downloader: ImageDownloader,
         didFinishDownloadingImageForURL url: URL,
         didFinishDownloadingImageForURL url: URL,
-        with response: URLResponse?, error: Error?)
+        with response: URLResponse?,
+        error: Error?)
+
+    /// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is
+    /// your last chance to verify or modify the downloaded data before Kingfisher tries to perform addition
+    /// processing on the image data.
+    ///
+    /// - Parameters:
+    ///   - downloader: The `ImageDownloader` object which is used for the downloading operation.
+    ///   - data: The original downloaded data.
+    ///   - url: The URL of the original request URL.
+    /// - Returns: The data from which Kingfisher should use to create an image. You need to provide valid data
+    ///            which content is one of the supported image file format. Kingfisher will perform process on this
+    ///            data and try to convert it to an image object.
+    /// - Note:
+    ///   This can be used to pre-process raw image data before creation of `Image` instance (i.e.
+    ///   decrypting or verification). If `nil` returned, the processing is interrupted and a `KingfisherError` with
+    ///   `ResponseErrorReason.dataModifyingFailed` will be raised. You could use this fact to stop the image
+    ///   processing flow if you find the data is corrupted or malformed.
+    func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data?
 
 
-    /// Called when the `ImageDownloader` object successfully downloaded and processed an image from specified URL.
+    /// Called when the `ImageDownloader` object successfully downloads and processes an image from specified URL.
     ///
     ///
     /// - Parameters:
     /// - Parameters:
     ///   - downloader: The `ImageDownloader` object which is used for the downloading operation.
     ///   - downloader: The `ImageDownloader` object which is used for the downloading operation.
@@ -66,8 +86,9 @@ public protocol ImageDownloaderDelegate: AnyObject {
         with response: URLResponse?)
         with response: URLResponse?)
 
 
     /// Checks if a received HTTP status code is valid or not.
     /// Checks 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.
+    /// By default, a status code in range 200..<400 is considered as valid.
+    /// If an invalid code is received, the downloader will raise an `KingfisherError` with
+    /// `ResponseErrorReason.invalidHTTPStatusCode` as its reason.
     ///
     ///
     /// - Parameters:
     /// - Parameters:
     ///   - code: The received HTTP status code.
     ///   - code: The received HTTP status code.
@@ -76,21 +97,9 @@ public protocol ImageDownloaderDelegate: AnyObject {
     /// - Note: If the default 200 to 400 valid code does not suit your need,
     /// - Note: If the default 200 to 400 valid code does not suit your need,
     ///         you can implement this method to change that behavior.
     ///         you can implement this method to change that behavior.
     func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool
     func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool
-
-    /// Called when the `ImageDownloader` object successfully downloaded image data from specified URL. This is
-    /// your last chance to modify the downloaded data before Kingfisher tries to perform addition processing on
-    /// the image data.
-    ///
-    /// - Parameters:
-    ///   - downloader: The `ImageDownloader` object which is used for the downloading operation.
-    ///   - data: Original downloaded data.
-    ///   - url: URL of the original request URL.
-    /// - Returns: The data from which Kingfisher would use to create an image.
-    /// - Note: This callback can be used to preprocess raw image data before creation of
-    ///         Image instance (i.e. decrypting or verification).
-    func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data?
 }
 }
 
 
+// Default implementation for `ImageDownloaderDelegate`.
 extension ImageDownloaderDelegate {
 extension ImageDownloaderDelegate {
     public func imageDownloader(
     public func imageDownloader(
         _ downloader: ImageDownloader,
         _ downloader: ImageDownloader,

+ 21 - 77
Sources/Networking/ImageModifier.swift

@@ -52,60 +52,35 @@ extension ImageModifier {
     }
     }
 }
 }
 
 
-typealias ModifierImp = ((Image) -> Image)
-
-fileprivate struct GeneralModifier: ImageModifier {
-    let identifier: String
-    let m: ModifierImp
-    func modify(_ image: Image) -> Image {
-        return m(image)
-    }
-}
-
 /// The default modifier.
 /// The default modifier.
-/// Does nothing and returns the image it was given
+/// It does nothing and returns the image as is.
 public struct DefaultImageModifier: ImageModifier {
 public struct DefaultImageModifier: ImageModifier {
 
 
     /// A default `DefaultImageModifier` which can be used everywhere.
     /// A default `DefaultImageModifier` which can be used everywhere.
     public static let `default` = DefaultImageModifier()
     public static let `default` = DefaultImageModifier()
-
-    /// Initialize a `DefaultImageModifier`
     private init() {}
     private init() {}
 
 
-    /// Modify an input `Image`.
-    ///
-    /// - parameter image:   Image which will be modified by `self`
-    ///
-    /// - returns: The modified image.
-    ///
-    /// - Note: See documentation of `ImageModifier` protocol for more.
-    public func modify(_ image: Image) -> Image {
-        return image
-    }
+    /// Modifie an input `Image`. See `ImageModifier` protocol for more.
+    public func modify(_ image: Image) -> Image { return image }
 }
 }
 
 
-/// A custom modifier.
-/// Can be initialized with a block to modify images in a custom way
+/// A wrapper for creating an `ImageModifier` easier.
+/// This type conforms to `ImageModifier` and wraps an image modify block.
+/// If the `block` throws an error, the original image will be used.
 public struct AnyImageModifier: ImageModifier {
 public struct AnyImageModifier: ImageModifier {
 
 
     /// A block which modifies images, or returns the original image
     /// A block which modifies images, or returns the original image
-    /// if modification cannot be performed.
-    let block: (Image) -> Image
+    /// if modification cannot be performed with an error.
+    let block: (Image) throws -> Image
 
 
-    /// Initialize an `AnyImageModifier`
-    public init(modify: @escaping (Image) -> Image) {
+    /// Creates an `AnyImageModifier` with a given `modify` block.
+    public init(modify: @escaping (Image) throws -> Image) {
         block = modify
         block = modify
     }
     }
 
 
-    /// Modifies an input `Image` using this `AnyImageModifier`'s `block`.
-    ///
-    /// - parameter image:   Image which will be modified by `self`
-    ///
-    /// - returns: The modified image.
-    ///
-    /// - Note: See documentation of `ImageModifier` protocol for more.
+    /// Modify an input `Image`. See `ImageModifier` protocol for more.
     public func modify(_ image: Image) -> Image {
     public func modify(_ image: Image) -> Image {
-        return block(image)
+        return (try? block(image)) ?? image
     }
     }
 }
 }
 
 
@@ -113,79 +88,48 @@ public struct AnyImageModifier: ImageModifier {
 import UIKit
 import UIKit
 
 
 /// Modifier for setting the rendering mode of images.
 /// Modifier for setting the rendering mode of images.
-/// Only UI-based images are supported; if a non-UI image is passed in, the
-/// modifier will do nothing.
 public struct RenderingModeImageModifier: ImageModifier {
 public struct RenderingModeImageModifier: ImageModifier {
 
 
     /// The rendering mode to apply to the image.
     /// The rendering mode to apply to the image.
     public let renderingMode: UIImage.RenderingMode
     public let renderingMode: UIImage.RenderingMode
 
 
-    /// Initialize a `RenderingModeImageModifier`
+    /// Creates a `RenderingModeImageModifier`.
     ///
     ///
-    /// - parameter renderingMode: The rendering mode to apply to the image.
-    ///                            Default is .automatic
+    /// - Parameter renderingMode: The rendering mode to apply to the image. Default is `.automatic`.
     public init(renderingMode: UIImage.RenderingMode = .automatic) {
     public init(renderingMode: UIImage.RenderingMode = .automatic) {
         self.renderingMode = renderingMode
         self.renderingMode = renderingMode
     }
     }
 
 
-    /// Modify an input `Image`.
-    ///
-    /// - parameter image:   Image which will be modified by `self`
-    ///
-    /// - returns: The modified image.
-    ///
-    /// - Note: See documentation of `ImageModifier` protocol for more.
+    /// Modify an input `Image`. See `ImageModifier` protocol for more.
     public func modify(_ image: Image) -> Image {
     public func modify(_ image: Image) -> Image {
         return image.withRenderingMode(renderingMode)
         return image.withRenderingMode(renderingMode)
     }
     }
 }
 }
 
 
 /// Modifier for setting the `flipsForRightToLeftLayoutDirection` property of images.
 /// Modifier for setting the `flipsForRightToLeftLayoutDirection` property of images.
-/// Only UI-based images are supported; if a non-UI image is passed in, the
-/// modifier will do nothing.
 public struct FlipsForRightToLeftLayoutDirectionImageModifier: ImageModifier {
 public struct FlipsForRightToLeftLayoutDirectionImageModifier: ImageModifier {
-    /// Initialize a `FlipsForRightToLeftLayoutDirectionImageModifier`
-    ///
-    /// - Note: On versions of iOS lower than 9.0, the image will be returned
-    ///         unmodified.
+
+    /// Creates a `FlipsForRightToLeftLayoutDirectionImageModifier`.
     public init() {}
     public init() {}
 
 
-    /// Modify an input `Image`.
-    ///
-    /// - parameter image:   Image which will be modified by `self`
-    ///
-    /// - returns: The modified image.
-    ///
-    /// - Note: See documentation of `ImageModifier` protocol for more.
+    /// Modify an input `Image`. See `ImageModifier` protocol for more.
     public func modify(_ image: Image) -> Image {
     public func modify(_ image: Image) -> Image {
-        if #available(iOS 9.0, *) {
-            return image.imageFlippedForRightToLeftLayoutDirection()
-        } else {
-            return image
-        }
+        return image.imageFlippedForRightToLeftLayoutDirection()
     }
     }
 }
 }
 
 
 /// Modifier for setting the `alignmentRectInsets` property of images.
 /// Modifier for setting the `alignmentRectInsets` property of images.
-/// Only UI-based images are supported; if a non-UI image is passed in, the
-/// modifier will do nothing.
 public struct AlignmentRectInsetsImageModifier: ImageModifier {
 public struct AlignmentRectInsetsImageModifier: ImageModifier {
 
 
     /// The alignment insets to apply to the image
     /// The alignment insets to apply to the image
     public let alignmentInsets: UIEdgeInsets
     public let alignmentInsets: UIEdgeInsets
 
 
-    /// Initialize a `AlignmentRectInsetsImageModifier`
+    /// Creates an `AlignmentRectInsetsImageModifier`.
     public init(alignmentInsets: UIEdgeInsets) {
     public init(alignmentInsets: UIEdgeInsets) {
         self.alignmentInsets = alignmentInsets
         self.alignmentInsets = alignmentInsets
     }
     }
 
 
-    /// Modify an input `Image`.
-    ///
-    /// - parameter image:   Image which will be modified by `self`
-    ///
-    /// - returns: The modified image.
-    ///
-    /// - Note: See documentation of `ImageModifier` protocol for more.
+    /// Modify an input `Image`. See `ImageModifier` protocol for more.
     public func modify(_ image: Image) -> Image {
     public func modify(_ image: Image) -> Image {
         return image.withAlignmentRectInsets(alignmentInsets)
         return image.withAlignmentRectInsets(alignmentInsets)
     }
     }

+ 32 - 31
Sources/Networking/ImagePrefetcher.swift

@@ -48,18 +48,21 @@ public typealias PrefetcherProgressBlock = ((_ skippedResources: [Resource], _ f
 public typealias PrefetcherCompletionHandler = ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void)
 public typealias PrefetcherCompletionHandler = ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> Void)
 
 
 /// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them.
 /// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them.
-/// This is useful when you know a list of image resources and want to download them before showing.
+/// This is useful when you know a list of image resources and want to download them before showing. It also works with
+/// some Cocoa prefetching mechanism like table view or collection view `prefetchDataSource`, to start image downloading
+/// and caching before they display on screen.
 public class ImagePrefetcher {
 public class ImagePrefetcher {
     
     
     /// The maximum concurrent downloads to use when prefetching images. Default is 5.
     /// The maximum concurrent downloads to use when prefetching images. Default is 5.
     public var maxConcurrentDownloads = 5
     public var maxConcurrentDownloads = 5
     
     
-    /// The dispatch queue to use for handling resource process so downloading does not occur on the main thread
-    /// This prevents stuttering when preloading images in a collection view or table view
+    // The dispatch queue to use for handling resource process, so downloading does not occur on the main thread
+    // This prevents stuttering when preloading images in a collection view or table view.
     private var prefetchQueue: DispatchQueue
     private var prefetchQueue: DispatchQueue
     
     
     private let prefetchResources: [Resource]
     private let prefetchResources: [Resource]
     private let optionsInfo: KingfisherOptionsInfo
     private let optionsInfo: KingfisherOptionsInfo
+
     private var progressBlock: PrefetcherProgressBlock?
     private var progressBlock: PrefetcherProgressBlock?
     private var completionHandler: PrefetcherCompletionHandler?
     private var completionHandler: PrefetcherCompletionHandler?
     
     
@@ -72,7 +75,7 @@ public class ImagePrefetcher {
     
     
     private var stopped = false
     private var stopped = false
     
     
-    // The created manager used for prefetch. We will use the helper method in manager.
+    // A manager used for prefetching. We will use the helper methods in manager.
     private let manager: KingfisherManager
     private let manager: KingfisherManager
     
     
     private var finished: Bool {
     private var finished: Bool {
@@ -80,11 +83,11 @@ public class ImagePrefetcher {
         return totalFinished == prefetchResources.count && tasks.isEmpty
         return totalFinished == prefetchResources.count && tasks.isEmpty
     }
     }
 
 
-    /// Init an image prefetcher with an array of URLs.
+    /// Creates an image prefetcher with an array of URLs.
     ///
     ///
     /// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable.
     /// The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable.
-    /// After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process.
-    /// The images already cached will be skipped without downloading again.
+    /// After you get a valid `ImagePrefetcher` object, you call `start()` on it to begin the prefetching process.
+    /// The images which are already cached will be skipped without downloading again.
     ///
     ///
     /// - Parameters:
     /// - Parameters:
     ///   - urls: The URLs which should be prefetched.
     ///   - urls: The URLs which should be prefetched.
@@ -96,7 +99,7 @@ public class ImagePrefetcher {
     /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
     /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
     /// the downloader and cache target respectively. You can specify another downloader or cache by using
     /// the downloader and cache target respectively. You can specify another downloader or cache by using
     /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
     /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
-    /// main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method.
+    /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
 
 
     public convenience init(urls: [URL],
     public convenience init(urls: [URL],
                          options: KingfisherOptionsInfo? = nil,
                          options: KingfisherOptionsInfo? = nil,
@@ -106,23 +109,20 @@ public class ImagePrefetcher {
         let resources: [Resource] = urls.map { $0 }
         let resources: [Resource] = urls.map { $0 }
         self.init(resources: resources, options: options, progressBlock: progressBlock, completionHandler: completionHandler)
         self.init(resources: resources, options: options, progressBlock: progressBlock, completionHandler: completionHandler)
     }
     }
-    
-    /**
-     Init an image prefetcher with an array of resources.
-     
-     The prefetcher should be initiated with a list of prefetching targets. The resources list is immutable.
-     After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process.
-     The images already cached will be skipped without downloading again.
-     
-     - parameter resources:         The resources which should be prefetched. See `Resource` type for more.
-     - parameter options:           A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
-     - parameter progressBlock:     Called every time an resource is downloaded, skipped or cancelled.
-     - parameter completionHandler: Called when the whole prefetching process finished.
 
 
-     - Note: By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
-     the downloader and cache target respectively. You can specify another downloader or cache by using a customized `KingfisherOptionsInfo`.
-     Both the progress and completion block will be invoked in main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method.
-     */
+    /// Creates an image prefetcher with an array of resources.
+    ///
+    /// - Parameters:
+    ///   - resources: The resources which should be prefetched. See `Resource` type for more.
+    ///   - options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
+    ///   - progressBlock: Called every time an resource is downloaded, skipped or cancelled.
+    ///   - completionHandler: Called when the whole prefetching process finished.
+    ///
+    /// - Note:
+    /// By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
+    /// the downloader and cache target respectively. You can specify another downloader or cache by using
+    /// a customized `KingfisherOptionsInfo`. Both the progress and completion block will be invoked in
+    /// main thread. The `.callbackQueue` value in `optionsInfo` will be ignored in this method.
     public init(resources: [Resource],
     public init(resources: [Resource],
                   options: KingfisherOptionsInfo? = nil,
                   options: KingfisherOptionsInfo? = nil,
             progressBlock: PrefetcherProgressBlock? = nil,
             progressBlock: PrefetcherProgressBlock? = nil,
@@ -135,11 +135,12 @@ public class ImagePrefetcher {
         let prefetchQueueName = "com.onevcat.Kingfisher.PrefetchQueue"
         let prefetchQueueName = "com.onevcat.Kingfisher.PrefetchQueue"
         prefetchQueue = DispatchQueue(label: prefetchQueueName)
         prefetchQueue = DispatchQueue(label: prefetchQueueName)
         
         
-        // We want all callbacks from our prefetch queue, so we should ignore the call back queue in options
+        // We want all callbacks from our prefetch queue, so we should ignore the callback queue in options.
         var optionsInfoWithoutQueue = (options ?? .empty)
         var optionsInfoWithoutQueue = (options ?? .empty)
             .removeAllMatchesIgnoringAssociatedValue(.callbackQueue(.untouch))
             .removeAllMatchesIgnoringAssociatedValue(.callbackQueue(.untouch))
         
         
-        // Add our own callback dispatch queue to make sure all callbacks are coming back in our expected queue
+        // Add our own callback dispatch queue to make sure all internal callbacks are
+        // coming back in our expected queue.
         optionsInfoWithoutQueue.append(.callbackQueue(.dispatch(prefetchQueue)))
         optionsInfoWithoutQueue.append(.callbackQueue(.dispatch(prefetchQueue)))
         
         
         optionsInfo = optionsInfoWithoutQueue
         optionsInfo = optionsInfoWithoutQueue
@@ -151,9 +152,8 @@ public class ImagePrefetcher {
         self.progressBlock = progressBlock
         self.progressBlock = progressBlock
         self.completionHandler = completionHandler
         self.completionHandler = completionHandler
     }
     }
-    
-    
-    /// Start to download the resources and cache them. This can be useful for background downloading
+
+    /// Starts to download the resources and cache them. This can be useful for background downloading
     /// of assets that are required for later use in an app. This code will not try and update any UI
     /// of assets that are required for later use in an app. This code will not try and update any UI
     /// with the results of the process.
     /// with the results of the process.
     public func start()
     public func start()
@@ -172,7 +172,8 @@ public class ImagePrefetcher {
                 self.handleComplete()
                 self.handleComplete()
                 return
                 return
             }
             }
-            
+
+            // Empty case.
             guard self.prefetchResources.count > 0 else {
             guard self.prefetchResources.count > 0 else {
                 self.handleComplete()
                 self.handleComplete()
                 return
                 return
@@ -187,7 +188,7 @@ public class ImagePrefetcher {
         }
         }
     }
     }
 
 
-    /// Stop current downloading progress, and cancel any future prefetching activity that might be occuring.
+    /// Stops current downloading progress, and cancel any future prefetching activity that might be occuring.
     public func stop() {
     public func stop() {
         prefetchQueue.async {
         prefetchQueue.async {
             if self.finished { return }
             if self.finished { return }

+ 17 - 14
Sources/Networking/SessionDataTask.swift

@@ -26,8 +26,11 @@
 
 
 import Foundation
 import Foundation
 
 
+/// Represents a session data task in `ImageDownloader`. It consists of an underlying `URLSessionDataTask` and
+/// an array of `TaskCallback`. Multiple `TaskCallback`s could be added for a single downloading data task.
 public class SessionDataTask {
 public class SessionDataTask {
 
 
+    /// Represents the type of token which used for cancelling a task.
     public typealias CancelToken = Int
     public typealias CancelToken = Int
 
 
     struct TaskCallback {
     struct TaskCallback {
@@ -36,28 +39,29 @@ public class SessionDataTask {
         let options: KingfisherOptionsInfo
         let options: KingfisherOptionsInfo
     }
     }
 
 
+    /// Downloaded raw data of current task.
     public private(set) var mutableData: Data
     public private(set) var mutableData: Data
-    public let task: URLSessionDataTask
 
 
+    // The underlying download task.
+    let task: URLSessionDataTask
     private var callbacksStore = [CancelToken: TaskCallback]()
     private var callbacksStore = [CancelToken: TaskCallback]()
 
 
     var callbacks: Dictionary<SessionDataTask.CancelToken, SessionDataTask.TaskCallback>.Values {
     var callbacks: Dictionary<SessionDataTask.CancelToken, SessionDataTask.TaskCallback>.Values {
         return callbacksStore.values
         return callbacksStore.values
     }
     }
 
 
-    var currentToken = 0
-
+    private var currentToken = 0
     private let lock = NSLock()
     private let lock = NSLock()
 
 
     let onTaskDone = Delegate<(Result<(Data, URLResponse?)>, [TaskCallback]), Void>()
     let onTaskDone = Delegate<(Result<(Data, URLResponse?)>, [TaskCallback]), Void>()
-    let onTaskCancelled = Delegate<(CancelToken, TaskCallback), Void>()
+    let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>()
 
 
     var started = false
     var started = false
     var containsCallbacks: Bool {
     var containsCallbacks: Bool {
         // We should be able to use `task.state != .running` to check it.
         // We should be able to use `task.state != .running` to check it.
         // However, in some rare cases, cancelling the task does not change
         // However, in some rare cases, cancelling the task does not change
-        // task state to `.cancelling`, but still in `.running`. So we need
-        // to check callbacks count to for sure that it is safe to remove the
+        // task state to `.cancelling` immediately, but still in `.running`.
+        // So we need to check callbacks count to for sure that it is safe to remove the
         // task in delegate.
         // task in delegate.
         return !callbacks.isEmpty
         return !callbacks.isEmpty
     }
     }
@@ -86,20 +90,19 @@ public class SessionDataTask {
     }
     }
 
 
     func resume() {
     func resume() {
+        guard !started else { return }
         started = true
         started = true
         task.resume()
         task.resume()
     }
     }
 
 
     func cancel(token: CancelToken) {
     func cancel(token: CancelToken) {
-        let result = removeCallback(token)
-        if let callback = result {
-
-            if callbacksStore.count == 0 {
-                task.cancel()
-            }
-
-            onTaskCancelled.call((token, callback))
+        guard let callback = removeCallback(token) else {
+            return
+        }
+        if callbacksStore.count == 0 {
+            task.cancel()
         }
         }
+        onCallbackCancelled.call((token, callback))
     }
     }
 
 
     func forceCancel() {
     func forceCancel() {

+ 4 - 6
Sources/Networking/SessionDelegate.swift

@@ -60,12 +60,14 @@ class SessionDelegate: NSObject {
         lock.lock()
         lock.lock()
         defer { lock.unlock() }
         defer { lock.unlock() }
 
 
+        // Try to reuse existing task.
         if let task = tasks[url] {
         if let task = tasks[url] {
             let token = task.addCallback(callback)
             let token = task.addCallback(callback)
             return DownloadTask(sessionTask: task, cancelToken: token)
             return DownloadTask(sessionTask: task, cancelToken: token)
         } else {
         } else {
+            // Create a new task if necessary.
             let task = SessionDataTask(task: dataTask)
             let task = SessionDataTask(task: dataTask)
-            task.onTaskCancelled.delegate(on: self) { [unowned task] (self, value) in
+            task.onCallbackCancelled.delegate(on: self) { [unowned task] (self, value) in
                 let (token, callback) = value
                 let (token, callback) = value
 
 
                 let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token))
                 let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token))
@@ -216,13 +218,9 @@ extension SessionDelegate: URLSessionDataDelegate {
         guard let sessionTask = self.task(for: task) else {
         guard let sessionTask = self.task(for: task) else {
             return
             return
         }
         }
-        onCompleted(sessionTask: sessionTask, result: result)
-    }
-
-    private func onCompleted(sessionTask: SessionDataTask, result: Result<(Data, URLResponse?)>) {
         // The lock should be already acquired in the session delege queue
         // The lock should be already acquired in the session delege queue
         // by the caller `urlSession(_:task:didCompleteWithError:)`.
         // by the caller `urlSession(_:task:didCompleteWithError:)`.
-        remove(sessionTask.task, acquireLock: false)
+        remove(task, acquireLock: false)
         sessionTask.onTaskDone.call((result, Array(sessionTask.callbacks)))
         sessionTask.onTaskDone.call((result, Array(sessionTask.callbacks)))
     }
     }
 }
 }