Просмотр исходного кода

Implement delegate for processor

onevcat 9 лет назад
Родитель
Сommit
01e5b0579e

+ 1 - 1
Kingfisher.xcodeproj/project.pbxproj

@@ -482,6 +482,7 @@
 		D10EC22A1C3D62D200A4211C /* Sources */ = {
 			isa = PBXGroup;
 			children = (
+				4B2B8E491D70128200FC4749 /* ImageProcessor.swift */,
 				4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */,
 				D10945EA1C526B6C001408EB /* Image.swift */,
 				D10945EB1C526B6C001408EB /* ImageCache.swift */,
@@ -609,7 +610,6 @@
 		D1ED2D021AD2CFA600CFC3EB = {
 			isa = PBXGroup;
 			children = (
-				4B2B8E491D70128200FC4749 /* ImageProcessor.swift */,
 				D10EC22A1C3D62D200A4211C /* Sources */,
 				D10EC22C1C3D62E800A4211C /* Tests */,
 				D10EC22B1C3D62DE00A4211C /* Demo */,

+ 73 - 19
Sources/ImageDownloader.swift

@@ -83,6 +83,7 @@ The error code.
 - invalidStatusCode: The HTTP status code in response is not valid. 
 - notCached: The image rquested is not in cache but .onlyFromCache is activated.
 - invalidURL: The URL is invalid.
+- downloadCanelledBeforeStarting: The downloading task is cancelled before started.
 */
 public enum KingfisherError: Int {
     case badData = 10000
@@ -90,12 +91,13 @@ public enum KingfisherError: Int {
     case invalidStatusCode = 10002
     case notCached = 10003
     case invalidURL = 20000
+    case downloadCanelledBeforeStarting = 30000
 }
 
 public let KingfisherErrorStatusCodeKey = "statusCode"
 
 /// Protocol of `ImageDownloader`.
-@objc public protocol ImageDownloaderDelegate {
+public protocol ImageDownloaderDelegate: class {
     /**
     Called when the `ImageDownloader` object successfully downloaded an image from specified URL.
     
@@ -104,7 +106,7 @@ public let KingfisherErrorStatusCodeKey = "statusCode"
     - parameter URL:        URL of the original request URL.
     - parameter response:   The response object of the downloading process.
     */
-    @objc optional func imageDownloader(_ downloader: ImageDownloader, didDownload image: Image, for url: URL, with response: URLResponse?)
+    func imageDownloader(_ downloader: ImageDownloader, didDownload image: Image, for url: URL, with response: URLResponse?)
     
     
     /**
@@ -114,13 +116,58 @@ public let KingfisherErrorStatusCodeKey = "statusCode"
     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.
     */
-    @objc optional func isValidStatusCode(_ code: Int) -> Bool
+    func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool
+    
+    /**
+     This method will be called before the download request sent. It's the last chance you can modify the request.
+     You can modify the request for some customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping.
+     
+     - parameter downloader: The `ImageDownloader` object which will send the request.
+     - parameter originalRequest: The original request which generated based on the image downloader. You can change the request based on this and return a new one.
+     
+     - returns: The new request which should be sent.
+     
+     - Note: If `nil` is returned, downloader will not start to send the request and callback handler will be invoked with a `downloadCanelledBeforeStarting` error.
+     */
+    func urlRequest(for downloader: ImageDownloader, byModifying originalRequest: URLRequest) -> URLRequest?
+    
+    /**
+     This method will be called after the downloading finishes, but before the data be converted to image.
+     If a cache is connected to the downloader (it happenes when you are using KingfisherManager or the image extension methods), 
+     the converted image will also be sent to cache and image view.
+     
+     The returned processor will be used to convert downloaded data to images. If the image delegate is `nil` or you do not implement this method, 
+     a `DefaultProcessor` will be used, which could process PNG, JPG and GIF images.
+     
+     - parameter downloader: The `ImageDownloader` object finishes the downloading.
+     - parameter task: The downloading task, from which you can get the downloading information including request and response.
+     
+     - returns: An instance that conforms to `ImageProcessor`. It will be used to process the downloaded data to an image.
+     */
+    func imageProcessor(for downloader: ImageDownloader, with task: URLSessionTask) -> ImageProcessor
+}
+
+extension ImageDownloaderDelegate {
+    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)
+    }
+    
+    public func urlRequest(for imageDownloader: ImageDownloader, byModifying originalRequest: URLRequest) -> URLRequest? {
+        return originalRequest
+    }
+    
+    public func imageProcessor(for downloader: ImageDownloader, with task: URLSessionTask) -> ImageProcessor {
+        return DefaultProcessor()
+    }
 }
 
 /// Protocol indicates that an authentication challenge could be handled.
@@ -170,7 +217,7 @@ public class ImageDownloader: NSObject {
     // MARK: - Public property
     /// This closure will be applied to the image download request before it being sent. 
     /// You can modify the request for some customizing purpose, like adding auth token to the header, do basic HTTP auth or something like url mapping.
-    
+    @available(*, unavailable, message: "`requestModifier` is removed. Use 'urlRequest(for:byModifying:)' from the 'ImageDownloaderDelegate' instead")
     public var requestModifier: ((inout URLRequest) -> Void)?
 
     /// The duration before the download is timeout. Default is 15 seconds.
@@ -304,8 +351,14 @@ extension ImageDownloader {
         // We need to set the URL as the load key. So before setup progress, we need to ask the `requestModifier` for a final URL.
         var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeout)
         request.httpShouldUsePipelining = requestsUsePipeling
-        
-        self.requestModifier?(&request)
+
+        if let delegate = delegate {
+            guard let r = delegate.urlRequest(for: self, byModifying: request) else {
+                completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.downloadCanelledBeforeStarting.rawValue, userInfo: nil), nil, nil)
+                return nil
+            }
+            request = r
+        }
         
         // There is a possiblility that request modifier changed the url to `nil` or empty.
         guard let url = request.url, !url.absoluteString.isEmpty else {
@@ -386,8 +439,14 @@ class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Authentic
     
     func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
         
+        guard let downloader = downloadHolder else {
+            completionHandler(.cancel)
+            return
+        }
+        
         if let statusCode = (response as? HTTPURLResponse)?.statusCode,
-                  let url = dataTask.originalRequest?.url, !isValidStatusCode(code: statusCode)
+           let url = dataTask.originalRequest?.url,
+            !(downloader.delegate ?? downloader).isValidStatusCode(statusCode, for: downloader)
         {
             callback(with: nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.invalidStatusCode.rawValue, userInfo: [KingfisherErrorStatusCodeKey: statusCode, NSLocalizedDescriptionKey: HTTPURLResponse.localizedString(forStatusCode: statusCode)]), url: url, originalData: nil)
         }
@@ -476,9 +535,10 @@ class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Authentic
             let options = fetchLoad.options ?? KingfisherEmptyOptionsInfo
             let data = fetchLoad.responseData as Data
             
-            if let image = DefaultProcessor().process(item: .data(data), options: options) {
+            let processer = (downloader.delegate ?? downloader).imageProcessor(for: downloader, with: task)
+            if let image = processer.process(item: .data(data), options: options) {
     
-                downloader.delegate?.imageDownloader?(downloader, didDownload: image, for: url, with: task.response)
+                downloader.delegate?.imageDownloader(downloader, didDownload: image, for: url, with: task.response)
                 
                 if options.backgroundDecode {
                     self.callback(with: image.kf_decoded(scale: options.scaleFactor), error: nil, url: url, originalData: data)
@@ -498,13 +558,7 @@ class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Authentic
             }
         }
     }
-    
-    private func isValidStatusCode(code: Int) -> Bool {
-        let inDefaultValidRange = (200..<400).contains(code)
-        if let delegate = downloadHolder?.delegate {
-            return delegate.isValidStatusCode?(code) ?? inDefaultValidRange
-        } else {
-            return inDefaultValidRange
-        }
-    }
 }
+
+// Placeholder. For retrieving extension methods of ImageDownloaderDelegate
+extension ImageDownloader: ImageDownloaderDelegate {}

+ 8 - 8
Sources/ImageProcessor.swift

@@ -8,12 +8,12 @@
 
 import Foundation
 
-enum ImageProcessItem {
+public enum ImageProcessItem {
     case image(Image)
     case data(Data)
 }
 
-protocol ImageProcessor {
+public protocol ImageProcessor {
     func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image?
 }
 
@@ -38,8 +38,8 @@ struct GeneralProcessor: ImageProcessor {
     }
 }
 
-struct DefaultProcessor: ImageProcessor {
-    func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? {
+public struct DefaultProcessor: ImageProcessor {
+    public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? {
         switch item {
         case .image(let image):
             return image
@@ -49,11 +49,11 @@ struct DefaultProcessor: ImageProcessor {
     }
 }
 
-struct RoundCornerImageProcessor: ImageProcessor {
+public struct RoundCornerImageProcessor: ImageProcessor {
     
-    let cornerRadius: Float
+    public let cornerRadius: Float
     
-    func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? {
+    public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? {
         switch item {
         case .image(let image):
             return image
@@ -64,6 +64,6 @@ struct RoundCornerImageProcessor: ImageProcessor {
 }
 
 infix operator |>: DefaultPrecedence
-func |>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor {
+public func |>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor {
     return left.append(another: right)
 }

+ 21 - 14
Tests/KingfisherTests/ImageDownloaderTests.swift

@@ -31,7 +31,9 @@ import XCTest
 class ImageDownloaderTests: XCTestCase {
 
     var downloader: ImageDownloader!
-
+    var modifier = URLModifier()
+    
+    
     override class func setUp() {
         super.setUp()
         LSNocilla.sharedInstance().start()
@@ -128,10 +130,8 @@ class ImageDownloaderTests: XCTestCase {
         let URLString = testKeys[0]
         _ = stubRequest("GET", URLString).andReturn(200)?.withBody(testImageData)
         
-        downloader.requestModifier = {
-            request in
-            request.url = URL(string: URLString)
-        }
+        modifier.url = URL(string: URLString)
+        downloader.delegate = modifier
         
         let someURL = URL(string: "some_strange_url")!
         downloader.downloadImage(with: someURL, options: nil, progressBlock: { (receivedSize, totalSize) -> () in
@@ -139,6 +139,7 @@ class ImageDownloaderTests: XCTestCase {
         }) { (image, error, imageURL, data) -> () in
             XCTAssert(image != nil, "Download should be able to finished for URL: \(imageURL).")
             XCTAssertEqual(imageURL!, URL(string: URLString)!, "The returned imageURL should be the replaced one")
+            self.downloader.delegate = nil
             expectation.fulfill()
         }
         waitForExpectations(timeout: 5, handler: nil)
@@ -228,9 +229,8 @@ class ImageDownloaderTests: XCTestCase {
     func testDownloadEmptyURL() {
         let expectation = self.expectation(description: "wait for downloading error")
         
-        downloader.requestModifier = { req in
-            req.url = nil
-        }
+        modifier.url = nil
+        downloader.delegate = modifier
         
         let url = URL(string: "http://onevcat.com")
         downloader.downloadImage(with: url!, progressBlock: { (receivedSize, totalSize) -> () in
@@ -238,9 +238,8 @@ class ImageDownloaderTests: XCTestCase {
             }) { (image, error, imageURL, originalData) -> () in
                 XCTAssertNotNil(error, "An error should happen for empty URL")
                 XCTAssertEqual(error!.code, KingfisherError.invalidURL.rawValue)
+                self.downloader.delegate = nil
                 expectation.fulfill()
-                
-                self.downloader.requestModifier = nil
         }
         waitForExpectations(timeout: 5, handler: nil)
     }
@@ -290,12 +289,20 @@ class ImageDownloaderTests: XCTestCase {
     }
     
     func testDownloadTaskNil() {
-        downloader.requestModifier = { req in
-            req.url = nil
-        }
+        modifier.url = nil
+        downloader.delegate = modifier
         let downloadTask = downloader.downloadImage(with: URL(string: "url")!, progressBlock: nil, completionHandler: nil)
         XCTAssertNil(downloadTask)
         
-        downloader.requestModifier = nil
+        downloader.delegate = nil
+    }
+}
+
+class URLModifier: ImageDownloaderDelegate {
+    var url: URL? = nil
+    func urlRequest(for imageDownloader: ImageDownloader, byModifying originalRequest: URLRequest) -> URLRequest? {
+        var r = originalRequest
+        r.url = url
+        return r
     }
 }