Przeglądaj źródła

Add common tasks and topics articles structure

onevcat 2 lat temu
rodzic
commit
782ab75ed9

+ 19 - 8
Sources/Documentation.docc/CommonTasks/CommonTasks.md

@@ -15,16 +15,27 @@ However, the similar code should also work for other platforms like macOS or tvO
 
 For common tasks of a specific part of Kingfisher, check the documentation below as well:
 
-- <doc:CommonTasks_>
+#### Common Tasks for Main Components
 
+- <doc:CommonTasks_Cache>
+- <doc:CommonTasks_Downloader>
+- <doc:CommonTasks_Processor>
 
+#### Other Topics
 
-## Most common tasks
+- <doc:Topic_Prefetch>
+- <doc:Topic_ImageDataProvider>
+- <doc:Topic_Indicator>
+- <doc:Topic_Retry>
+- <doc:Topic_LowDataMode> 
+- <doc:Topic_PerformanceTips>
+
+## Most Common Tasks
 
 The view extension based APIs (for `UIImageView`, `NSImageView`, `UIButton` and `NSButton`) should be your first choice
 whenever possible. It keeps your code simple and elegant.
 
-### Setting image with a `URL`
+### Setting Image with a `URL`
 
 ```swift
 let url = URL(string: "https://example.com/image.jpg")
@@ -44,7 +55,7 @@ Later, when you call the `setImage` with the same url again, only step 1 and 2 w
 purged.
 
 
-### Showing a placeholder
+### Showing a Placeholder
 
 ```swift
 let image = UIImage(named: "default_profile_icon")
@@ -65,7 +76,7 @@ The `image` will show in the `imageView` while downloading from `url`.
 >
 > The `MyView` instance will be added to / removed from the `imageView` as needed.
 
-### Showing a loading indicator while downloading
+### Showing a Loading Indicator while Downloading
 
 ```swift
 imageView.kf.indicatorType = .activity
@@ -74,13 +85,13 @@ imageView.kf.setImage(with: url)
 
 Show a `UIActivityIndicatorView` in center of image view while downloading.
 
-### Fading in downloaded image
+### Fading in Downloaded Image
 
 ```swift
 imageView.kf.setImage(with: url, options: [.transition(.fade(0.2))])
 ```
 
-### Completion handler
+### Completion Handler
 
 ```swift
 imageView.kf.setImage(with: url) { result in
@@ -105,7 +116,7 @@ imageView.kf.setImage(with: url) { result in
 }
 ```
 
-### Getting an image without setting to UI
+### Getting an Image without Setting to UI
 
 Sometimes, you just want to get the image with Kingfisher instead of setting it to an image view. Use `KingfisherManager` for it:
 

+ 48 - 2
Sources/Documentation.docc/CommonTasks/CommonTasks_Cache.md

@@ -1,6 +1,6 @@
-# CommonTasks - Cache
+# Common Tasks - Cache
 
-Common tasks related to the `ImageCache` in Kingfisher.
+Common tasks related to the ``ImageCache`` in Kingfisher.
 
 ## Overview
 
@@ -177,4 +177,50 @@ let cache = ImageCache(name: "my-own-cache")
 imageView.kf.setImage(with: url, options: [.targetCache(cache)])
 ```
 
+#### Skipping cache searching, force downloading image again 
 
+```swift
+imageView.kf.setImage(with: url, options: [.forceRefresh])
+```
+
+#### Only search cache for the image, do not download if not existing
+
+This makes your app to an "offline" mode.
+
+```swift
+imageView.kf.setImage(with: url, options: [.onlyFromCache])
+```
+
+If the image is not existing in the cache, a `.cacheError` with `.imageNotExisting` reason will be raised.
+
+#### Waiting for cache finishing
+
+Storing images to disk cache is an asynchronous operation. However, it is not required to be done before we can set the image view and call the completion handler in view extension methods. In other words, the disk cache may not exist yet in the completion handler below:
+
+```swift
+imageView.kf.setImage(with: url) { _ in
+    ImageCache.default.retrieveImageInDiskCache(forKey: url.cacheKey) { result in
+        switch result {
+        case .success(let image):
+            // `image` might be `nil` here.
+        case .failure: break
+        }
+    }
+}
+```
+
+This is not a problem for most use cases. However, if your logic depends on the existing of disk cache, pass `.waitForCache` as an option. Kingfisher will then wait until the disk cache finishes before calling the handler:
+
+```swift
+imageView.kf.setImage(with: url, options: [.waitForCache]) { _ in
+    ImageCache.default.retrieveImageInDiskCache(forKey: url.cacheKey) { result in
+        switch result {
+        case .success(let image):
+            // `image` exists.
+        case .failure: break
+        }
+    }
+}
+```
+
+This is only for disk image cache, which involves to async I/O. For the memory cache, everything goes synchronously, so the image should be always in the memory cache.

+ 164 - 0
Sources/Documentation.docc/CommonTasks/CommonTasks_Downloader.md

@@ -0,0 +1,164 @@
+# Common Tasks - Downloader
+
+Common tasks related to the ``ImageDownloader`` in Kingfisher.
+
+## Overview
+
+``ImageDownloader`` wraps a `URLSession` for downloading an image from the Internet. Similar to ``ImageCache``, there
+is a ``ImageDownloader/default`` downloader for downloading tasks.
+
+### Downloading an image manually
+
+Usually, you may use Kingfisher's view extension methods or `KingfisherManager` to retrieve an image. They will try to search in the cache first to prevent unnecessary download task. In some cases, if you only want to download a target image without caching it:
+
+```swift
+let downloader = ImageDownloader.default
+downloader.downloadImage(with: url) { result in
+    switch result {
+    case .success(let value):
+        print(value.image)
+    case .failure(let error):
+        print(error)
+    }
+}
+```
+
+### Modify a Request Before Sending
+
+When you have permission control for your image resource, you can modify the request by using a `.requestModifier`:
+
+```swift
+let modifier = AnyModifier { request in
+    var r = request
+    r.setValue("abc", forHTTPHeaderField: "Access-Token")
+    return r
+}
+downloader.downloadImage(with: url, options: [.requestModifier(modifier)]) { 
+    result in
+    // ...
+}
+
+// This option also works for view extension methods.
+imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])
+```
+
+### Async Request Modifier
+
+If you need to perform some asynchronous operation before modifying the request, create a type and conform to `AsyncImageDownloadRequestModifier`:
+
+```swift
+class AsyncModifier: AsyncImageDownloadRequestModifier {
+    var onDownloadTaskStarted: ((DownloadTask?) -> Void)?
+
+    func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) {
+        var r = request
+        someAsyncOperation { result in
+            r.someProperty = result.property
+            reportModified(r)
+        }
+    }
+}
+```
+
+Similar as above, you can use the `.requestModifier` to use this modifier. In this case, the `setImage(with:options:)` or `ImageDownloader.downloadImage(with:options:)` method will not return a `DownloadTask` anymore (since it does not start the download task immediately). Instead, you observe one from the `onDownloadTaskStarted` callback if you need a reference to the task:
+
+```swift
+let modifier = AsyncModifier()
+modifier.onDownloadTaskStarted = { task in
+    if let task = task {
+        print("A download task started: \(task)")
+    }
+}
+let nilTask = imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])
+```
+
+### Cancelling a Download Task
+
+If the downloading started, a `DownloadTask` will be returned. You can use it to cancel an on-going download task:
+
+```swift
+let task = downloader.downloadImage(with: url) { result in
+    // ...
+    case .failure(let error):
+        print(error.isTaskCancelled) // true
+    }
+
+}
+
+// After for a while, before download task finishes.
+task?.cancel()
+```
+
+If the task already finished when you call `task?.cancel()`, nothing will happen.
+
+Similar, the view extension methods also return `DownloadTask`. You can store and cancel it:
+
+```swift
+let task = imageView.kf.set(with: url)
+task?.cancel()
+```
+
+Or, you can call `cancelDownloadTask` on the image view to cancel the **current downloading task**:
+
+```swift
+let task1 = imageView.kf.set(with: url1)
+let task2 = imageView.kf.set(with: url2)
+
+imageView.kf.cancelDownloadTask()
+// `task2` will be cancelled, but `task1` is still running. 
+// However, the downloaded image for `task1` will not be set because the image view expects a result from `url2`.
+```
+
+### Authentication with `NSURLCredential`
+
+The `ImageDownloader` uses a default behavior (`.performDefaultHandling`) when receives a challenge from server. If you need to provide your own credentials, setup an `authenticationChallengeResponder`:
+
+```swift
+// In ViewController
+ImageDownloader.default.authenticationChallengeResponder = self
+
+extension ViewController: AuthenticationChallengeResponsable {
+
+    var disposition: URLSession.AuthChallengeDisposition { /* */ }
+    let credential: URLCredential? { /* */ }
+
+    func downloader(
+        _ downloader: ImageDownloader,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
+    {
+        // Provide your `AuthChallengeDisposition` and `URLCredential`
+        completionHandler(disposition, credential)
+    }
+
+    func downloader(
+        _ downloader: ImageDownloader,
+        task: URLSessionTask,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
+    {
+        // Provide your `AuthChallengeDisposition` and `URLCredential`
+        completionHandler(disposition, credential)
+    }
+}
+```
+
+### Download Timeout
+
+By default, the download timeout for a request is 15 seconds. To set it for the downloader:
+
+```swift
+// Set timeout to 1 minute.
+downloader.downloadTimeout = 60
+```
+
+To define a timeout for a certain request, use a `.requestModifier`:
+
+```swift
+let modifier = AnyModifier { request in
+    var r = request
+    r.timeoutInterval = 60
+    return r
+}
+downloader.downloadImage(with: url, options: [.requestModifier(modifier)])
+```

+ 112 - 0
Sources/Documentation.docc/CommonTasks/CommonTasks_Processor.md

@@ -0,0 +1,112 @@
+# Common tasks - Processor
+
+Common tasks related to the ``ImageProcessor`` in Kingfisher.
+
+## Overview
+
+``ImageProcessor`` transforms an image (or data) to another image. You can provide a processor to ``ImageDownloader`` 
+to  apply it to the downloaded data. Then processed image will be sent to the image view and the cache.
+
+### Use the Default Processor
+
+```swift
+// Just without anything
+imageView.kf.setImage(with: url)
+// It equals to
+imageView.kf.setImage(with: url, options: [.processor(DefaultImageProcessor.default)])
+```
+
+> `DefaultImageProcessor` converts downloaded data to a corresponded image object. PNG, JPEG, and GIF are supported.
+
+### Built-in Processors
+
+```swift
+// Round corner
+let processor = RoundCornerImageProcessor(cornerRadius: 20)
+
+// Downsampling
+let processor = DownsamplingImageProcessor(size: CGSize(width: 100, height: 100))
+
+// Cropping
+let processor = CroppingImageProcessor(size: CGSize(width: 100, height: 100), anchor: CGPoint(x: 0.5, y: 0.5))
+
+// Blur
+let processor = BlurImageProcessor(blurRadius: 5.0)
+
+// Overlay with a color & fraction
+let processor = OverlayImageProcessor(overlay: .red, fraction: 0.7)
+
+// Tint with a color
+let processor = TintImageProcessor(tint: .blue)
+
+// Adjust color
+let processor = ColorControlsProcessor(brightness: 1.0, contrast: 0.7, saturation: 1.1, inputEV: 0.7)
+
+// Black & White
+let processor = BlackWhiteProcessor()
+
+// Blend (iOS)
+let processor = BlendImageProcessor(blendMode: .darken, alpha: 1.0, backgroundColor: .lightGray)
+
+// Compositing
+let processor = CompositingImageProcessor(compositingOperation: .darken, alpha: 1.0, backgroundColor: .lightGray)
+
+// Use the process in view extension methods.
+imageView.kf.setImage(with: url, options: [.processor(processor)])
+```
+
+### Multiple Processors
+
+```swift
+// First blur the image, then make it round cornered.
+let processor = BlurImageProcessor(blurRadius: 4) |> RoundCornerImageProcessor(cornerRadius: 20)
+imageView.kf.setImage(with: url, options: [.processor(processor)])
+```
+
+### Creating Your Own Processor
+
+Make a type conforming to `ImageProcessor` by implementing `identifier` and `process`:
+
+```swift
+struct MyProcessor: ImageProcessor {
+
+    // `identifier` should be the same for processors with the same properties/functionality
+    // It will be used when storing and retrieving the image to/from cache.
+    let identifier = "com.yourdomain.myprocessor"
+    
+    // Convert input data/image to target image and return it.
+    func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> Image? {
+        switch item {
+        case .image(let image):
+            // A previous processor already converted the image to an image object.
+            // You can do whatever you want to apply to the image and return the result.
+            return image
+        case .data(let data):
+            // Your own way to convert some data to an image.
+            return createAnImage(data: data)
+        }
+    }
+}
+
+// Then pass it to the `setImage` methods:
+let processor = MyProcessor()
+let url = URL(string: "https://example.com/my_image.png")
+imageView.kf.setImage(with: url, options: [.processor(processor)])
+```
+
+### Creating a Processor from `CIFilter`
+
+If you have a prepared `CIFilter`, you can create a processor quickly from it.
+
+```swift
+struct MyCIFilter: CIImageProcessor {
+
+    let identifier = "com.yourdomain.myCIFilter"
+    
+    let filter = Filter { input in
+        guard let filter = CIFilter(name: "xxx") else { return nil }
+        filter.setValue(input, forKey: kCIInputBackgroundImageKey)
+        return filter.outputImage
+    }
+}
+```

+ 50 - 0
Sources/Documentation.docc/CommonTasks/CommonTasks_Serializer.md

@@ -0,0 +1,50 @@
+# Common Tasks - Serializer
+
+``CacheSerializer`` will be used to convert some data to an image object for retrieving from disk cache and vice versa 
+for storing to the disk cache.
+
+### Use the Default Serializer
+
+```swift
+// Just without anything
+imageView.kf.setImage(with: url)
+// It equals to
+imageView.kf.setImage(with: url, options: [.cacheSerializer(DefaultCacheSerializer.default)])
+```
+
+> `DefaultCacheSerializer` converts cached data to a corresponded image object and vice versa. PNG, JPEG, and GIF are supported by default.
+
+### Serializer to Force a Format
+
+To specify a certain format an image should be, use `FormatIndicatedCacheSerializer`. It provides serializers for all built-in supported format: `FormatIndicatedCacheSerializer.png`, `FormatIndicatedCacheSerializer.jpeg` and `FormatIndicatedCacheSerializer.gif`.
+
+By using the `DefaultCacheSerializer`, Kingfisher respects the input image data format and try to keep it unchanged. However, sometimes this default behavior might be not what you want. A common case is that, when you using a `RoundCornerImageProcessor`, in most cases maybe you want to have an alpha channel (for the corner part). If your original image is JPEG, the alpha channel would be lost when storing to disk. In this case, you may also want to set the png serializer to force converting the images to PNG:
+
+```swift
+let roundCorner = RoundCornerImageProcessor(cornerRadius: 20)
+imageView.kf.setImage(with: url, 
+    options: [.processor(roundCorner), 
+              .cacheSerializer(FormatIndicatedCacheSerializer.png)]
+)
+```
+
+### Creating Your Own Serializer
+
+Make a type conforming to `CacheSerializer` by implementing `data(with:original:)` and `image(with:options:)`:
+
+```swift
+struct MyCacheSerializer: CacheSerializer {
+    func data(with image: Image, original: Data?) -> Data? {
+        return MyFramework.data(of: image)
+    }
+    
+    func image(with data: Data, options: KingfisherParsedOptionsInfo?) -> Image? {
+        return MyFramework.createImage(from: data)
+    }
+}
+
+// Then pass it to the `setImage` methods:
+let serializer = MyCacheSerializer()
+let url = URL(string: "https://yourdomain.com/example.png")
+imageView.kf.setImage(with: url, options: [.cacheSerializer(serializer)])
+```

+ 94 - 0
Sources/Documentation.docc/Topics/Topic_ImageDataProvider.md

@@ -0,0 +1,94 @@
+# Loading a local image or loading from data
+
+## Overview
+
+Kingfisher can set images from a local data source, enabling the processing and management of local image data using 
+Kingfisher's features, without requiring network downloads.
+
+### Image from Local File
+
+`LocalFileImageDataProvider` is a type conforming to `ImageDataProvider`. It is used to load an image from a local file URL:
+
+```swift
+let url = URL(fileURLWithPath: path)
+let provider = LocalFileImageDataProvider(fileURL: url)
+imageView.kf.setImage(with: provider)
+```
+
+You can also pass options to it:
+
+```swift
+let processor = RoundCornerImageProcessor(cornerRadius: 20)
+imageView.kf.setImage(with: provider, options: [.processor(processor)])
+```
+
+### Image from Base64 String
+
+Use `Base64ImageDataProvider` to provide an image from base64 encoded data. All other features you expected, such as cache or image processors, should work as they are as when getting images from an URL.
+
+```swift
+let provider = Base64ImageDataProvider(base64String: "\/9j\/4AAQSkZJRgABAQA...", cacheKey: "some-cache-key")
+imageView.kf.setImage(with: provider)
+```
+
+### Generating Image from AVAsset
+
+Use `AVAssetImageDataProvider` to generate an image from a video URL or `AVAsset` at a given time:
+
+```swift
+let provider = AVAssetImageDataProvider(
+    assetURL: URL(string: "https://example.com/your_video.mp4")!,
+    seconds: 15.0
+)
+```
+
+### Creating Your Own Image Data Provider
+
+If you want to create your own image data provider type, conform to `ImageDataProvider` protocol by implementing a `cacheKey` and a `data(handler:)` method to provide image data:
+
+```swift
+struct UserNameLetterIconImageProvider: ImageDataProvider {
+    var cacheKey: String { return letter }
+    let letter: String
+    
+    init(userNameFirstLetter: String) {
+        self.letter = userNameFirstLetter
+    }
+    
+    func data(handler: @escaping (Result<Data, Error>) -> Void) {
+        
+        // You can ignore these detail below.
+        // It generates some data for an image with `letter` being rendered in the center.
+
+        let letter = self.letter as NSString
+        let rect = CGRect(x: 0, y: 0, width: 250, height: 250)
+        let renderer = UIGraphicsImageRenderer(size: rect.size)
+        let data = renderer.pngData { context in
+            UIColor.black.setFill()
+            context.fill(rect)
+            
+            let attributes = [
+                NSAttributedString.Key.foregroundColor: UIColor.white,
+                                      .font: UIFont.systemFont(ofSize: 200)
+            ]
+            
+            let textSize = letter.size(withAttributes: attributes)
+            let textRect = CGRect(
+                x: (rect.width - textSize.width) / 2,
+                y: (rect.height - textSize.height) / 2,
+                width: textSize.width,
+                height: textSize.height)
+            letter.draw(in: textRect, withAttributes: attributes)
+        }
+
+        // Provide the image data in `handler`.
+        handler(.success(data))
+    }
+}
+
+// Set image for user "John"
+let provider = UserNameLetterIconImageProvider(userNameFirstLetter: "J")
+imageView.kf.setImage(with: provider)
+```
+
+Maybe you have already noticed, the `data(handler:)` contains a callback to you. You can provide the image data in an asynchronous way from another thread if it is too heavy in the main thread.

+ 42 - 0
Sources/Documentation.docc/Topics/Topic_Indicator.md

@@ -0,0 +1,42 @@
+# Setting and customizing indicator while loading
+
+#### Using an Image as Indicator
+
+```swift
+let path = Bundle.main.path(forResource: "loader", ofType: "gif")!
+let data = try! Data(contentsOf: URL(fileURLWithPath: path))
+
+imageView.kf.indicatorType = .image(imageData: data)
+imageView.kf.setImage(with: url)
+```
+
+#### Using a Customized View
+
+```swift
+struct MyIndicator: Indicator {
+    let view: UIView = UIView()
+    
+    func startAnimatingView() { view.isHidden = false }
+    func stopAnimatingView() { view.isHidden = true }
+    
+    init() {
+        view.backgroundColor = .red
+    }
+}
+
+let i = MyIndicator()
+imageView.kf.indicatorType = .custom(indicator: i)
+```
+
+#### Updating Indicator with Percentage
+
+```swift
+imageView.kf.setImage(with: url, progressBlock: {
+    receivedSize, totalSize in
+    let percentage = (Float(receivedSize) / Float(totalSize)) * 100.0
+    print("downloading progress: \(percentage)%")
+    myIndicator.percentage = percentage
+})
+```
+
+The `progressBlock` will be only called if your server response contains the "Content-Length" in the header.

+ 29 - 0
Sources/Documentation.docc/Topics/Topic_LowDataMode.md

@@ -0,0 +1,29 @@
+# Loading Image for Low Data Mode
+
+## Overview
+
+From iOS 13, Apple allows user to choose to turn on [Low Data Mode] to save cellular and Wi-Fi usage. To respect this setting, you can provide an alternative (usually low-resolution) version of the image and Kingfisher will use that when Low Data Mode is enabled:
+
+```swift
+imageView.kf.setImage(
+    with: highResolutionURL, 
+    options: [.lowDataSource(.network(lowResolutionURL)]
+)
+```
+
+If there is no network restriction applied by user, `highResolutionURL` will be used. Otherwise, when the device is under Low Data Mode and the `highResolutionURL` version is not hit in the cache, `lowResolutionURL` will be used.
+
+Since `.lowDataSource` accept any `Source` parameter instead of only a URL, you can also pass in a local image provider to prevent any downloading task:
+
+```swift
+imageView.kf.setImage(
+    with: highResolutionURL, 
+    options: [
+        .lowDataSource(
+            .provider(LocalFileImageDataProvider(fileURL: localFileURL))
+        )
+    ]
+)
+```
+
+> If `.lowDataSource` option is not provided, the `highResolutionURL` will be always used, regardless of the Low Data Mode setting on the device.

+ 98 - 0
Sources/Documentation.docc/Topics/Topic_PerformanceTips.md

@@ -0,0 +1,98 @@
+# Performance Tips
+
+Some useful tips for better performance when using Kingfisher.
+
+#### Cancelling unnecessary downloading tasks
+
+Once a downloading task initialized, even when you set another URL to the image view, that task will continue until finishes.
+
+```swift
+imageView.kf.setImage(with: url1) { result in 
+    // `result` is `.failure(.imageSettingError(.notCurrentSourceTask))`
+    // But the download (and cache) is done.
+}
+
+// Set again immediately.
+imageView.kf.setImage(with: url2) { result in 
+    // `result` is `.success`
+}
+```
+
+Although setting for `url1` results in a `.failure` since the setting task was overridden by `url2`, the download task itself is finished. The downloaded image data is also processed and cached.
+
+The downloading and caching operation for the image at `url1` is not free, it costs network, CPU time, memory and also, battery. 
+
+In most cases, it worths to do that. Since there is a chance that the image is shown to the user again. But if you are sure that you do not need the image from `url1`, you can cancel the downloading before starting another one:
+
+```swift
+imageView.kf.setImage(with: ImageLoader.sampleImageURLs[8]) { result in
+    // `result` is `.failure(.requestError(.taskCancelled))`
+    // Now the download task is cancelled.
+}
+
+imageView.kf.cancelDownloadTask()
+imageView.kf.setImage(with: ImageLoader.sampleImageURLs[9]) { result in
+    // `result` is `.success`
+}
+```
+
+This technology sometimes is useful in a table view or collection view. When users scrolling the list fast, maybe quite a lot of image downloading tasks would be created. You can cancel unnecessary tasks in the `didEndDisplaying` delegate method:
+
+```swift
+func collectionView(
+    _ collectionView: UICollectionView,
+    didEndDisplaying cell: UICollectionViewCell,
+    forItemAt indexPath: IndexPath)
+{
+    // This will cancel the unfinished downloading task when the cell disappearing.
+    cell.imageView.kf.cancelDownloadTask()
+}
+```
+
+#### Using processor with `ImageCache`
+
+Kingfisher is smart enough to cache the processed images and then get it back if you specify the correct `ImageProcessor` in the option. Each `ImageProcessor` contains an `identifier`. It is used when caching the processed images.
+
+Without the `identifier`, Kingfisher will not be able to tell which is the correct image in cache. Think about the case you have to store two versions of an image from the same url, one should be round cornered and another should be blurred. You need two different cache keys. In all Kingfisher's built-in image processors, the identifier will be determined by the kind of processor, combined with its parameters for each instance. For example, a round corner processor with 20 as its corner radius might have an `identifier` as `round-corner-20`, while a 40 radius one's could be `round-corner-40`. (Just for demonstrating, they are not that simple value in real)
+
+So, when you create your own processor, you need to make sure that you provide a different `identifier` for any different processor instance, with its parameter considered. This helps the processors work well with the cache. Furthermore, it prevents unnecessary downloading and processing.
+
+#### Cache original image when using a processor
+
+If you are trying to do one of these:
+
+1. Process the same image with different processors to get different versions of the image.
+2. Process an image with a processor other than the default one, and later need to display the original image.
+
+It worths passing `.cacheOriginalImage` as an option. This will store the original downloaded image to cache as well:
+
+```swift
+let p1 = MyProcessor()
+imageView.kf.setImage(with: url, options: [.processor(p1), .cacheOriginalImage])
+```
+
+Both the processed image by `p1` and the original downloaded image will be cached. Later, when you process with another processor:
+
+```swift
+let p2 = AnotherProcessor()
+imageView.kf.setImage(with: url, options: [.processor(p2)])
+```
+
+The processed image for `p2` is not in cache yet, but Kingfisher now has a chance to check whether the original image for `url` being in cache or not. Instead of downloading the image again, Kingfisher will reuse the original image and then apply `p2` on it directly.
+
+#### Using `DownsamplingImageProcessor` for high resolution images
+
+Think about the case we want to show some large images in a table view or a collection view. In the ideal world, we expect to get smaller thumbnails for them, to reduce downloading time and memory use. But in the real world, maybe your server doesn't prepare such a thumbnail version for you. The newly added `DownsamplingImageProcessor` rescues. It downsamples the high-resolution images to a certain size before loading to memory:
+
+```swift
+imageView.kf.setImage(
+    with: resource,
+    placeholder: placeholderImage,
+    options: [
+        .processor(DownsamplingImageProcessor(size: imageView.size)),
+        .scaleFactor(UIScreen.main.scale),
+        .cacheOriginalImage
+    ])
+```
+
+Typically, `DownsamplingImageProcessor` is used with `.scaleFactor` and `.cacheOriginalImage`. It provides a reasonable image pixel scale for your UI, and prevent future downloading by caching the original high-resolution image.

+ 44 - 0
Sources/Documentation.docc/Topics/Topic_Prefetch.md

@@ -0,0 +1,44 @@
+# Prefetching images before actually loading  
+
+Loading images before actually needed. Feed them to the table view or collection view.
+
+## Overview
+
+You could use ``ImagePrefetcher`` to prefetch some images and cache them before you display them on the screen. This is
+useful when you know a list of image resources you know they would probably be shown later.
+
+### Prefetch some Images
+
+```swift
+let urls = ["https://example.com/image1.jpg", "https://example.com/image2.jpg"]
+           .map { URL(string: $0)! }
+let prefetcher = ImagePrefetcher(urls: urls) {
+    skippedResources, failedResources, completedResources in
+    print("These resources are prefetched: \(completedResources)")
+}
+prefetcher.start()
+
+// Later when you need to display these images:
+imageView.kf.setImage(with: urls[0])
+anotherImageView.kf.setImage(with: urls[1])
+```
+
+### Prefetch Images for Table View or Collection View
+
+From iOS 10, Apple introduced a cell prefetching behavior. It could work seamlessly with Kingfisher's `ImagePrefetcher`.
+
+```swift
+override func viewDidLoad() {
+    super.viewDidLoad()
+    collectionView?.prefetchDataSource = self
+}
+
+extension ViewController: UICollectionViewDataSourcePrefetching {
+    func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
+        let urls = indexPaths.flatMap { URL(string: $0.urlString) }
+        ImagePrefetcher(urls: urls).start()
+    }
+}
+```
+
+See [WWDC 16 - Session 219](https://developer.apple.com/videos/play/wwdc2016/219/) for more about changing of it in iOS 10.

+ 20 - 0
Sources/Documentation.docc/Topics/Topic_Retry.md

@@ -0,0 +1,20 @@
+# Controlling the Retry Mechanism when Error Happens
+
+Use ``KingfisherOptionsInfoItem/retryStrategy(_:)`` and ``RetryStrategy`` to implement a simple retry mechanism 
+when setting an image and an error happens.
+
+## Basic Retry Strategy
+
+Use ``KingfisherOptionsInfoItem/retryStrategy(_:)`` and ``DelayRetryStrategy`` to implement a simple retry mechanism 
+when setting an image:
+
+```swift
+let retry = DelayRetryStrategy(maxRetryCount: 5, retryInterval: .seconds(3))
+imageView.kf.setImage(with: url, options: [.retryStrategy(retry)])
+```
+
+This will retry the target URL for at most 5 times, with a constant 3 seconds as the interval between each attempt.
+You can also choose an `.accumulated(3)` as the retry interval, which gives you an accumulated `3 -> 6 -> 9 -> 12 -> 15` 
+retry interval. Or you can even define your own interval pattern by `.custom`.
+
+If you need more control for the retry strategy, implement your own type conforming to the [`RetryStrategy` protocol](https://swiftpackageindex.com/onevcat/Kingfisher/master/documentation/kingfisher/retrystrategy/).