Преглед изворни кода

Merge pull request #1373 from onevcat/feature/customizable-default-cache-serializer

Feature customizable default cache serializer
Wei Wang пре 6 година
родитељ
комит
08136c7d2b

+ 29 - 3
Sources/Cache/CacheSerializer.swift

@@ -25,6 +25,7 @@
 //  THE SOFTWARE.
 
 import Foundation
+import CoreGraphics
 
 /// An `CacheSerializer` is used to convert some data to an image object after
 /// retrieving it from disk storage, and vice versa, to convert an image to data object
@@ -80,8 +81,22 @@ public struct DefaultCacheSerializer: CacheSerializer {
     
     /// The default general cache serializer used across Kingfisher's cache.
     public static let `default` = DefaultCacheSerializer()
-    private init() {}
-    
+
+    /// The compression quality when converting image to a lossy format data. Default is 1.0.
+    public var compressionQuality: CGFloat = 1.0
+
+    /// Whether the original data should be preferred when serializing the image.
+    /// If `true`, the input original data will be checked first and used unless the data is `nil`.
+    /// In that case, the serialization will fall back to creating data from image.
+    public var preferCacheOriginalData: Bool = false
+
+    /// Creates a cache serializer that serialize and deserialize images in PNG, JPEG and GIF format.
+    ///
+    /// - Note:
+    /// Use `DefaultCacheSerializer.default` unless you need to specify your own properties.
+    ///
+    public init() { }
+
     /// - Parameters:
     ///   - image: The image needed to be serialized.
     ///   - original: The original data which is just downloaded.
@@ -95,7 +110,18 @@ public struct DefaultCacheSerializer: CacheSerializer {
     /// converted to the corresponding data type. Otherwise, if the `original` is provided but it is not
     /// If `original` is `nil`, the input `image` will be encoded as PNG data.
     public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? {
-        return image.kf.data(format: original?.kf.imageFormat ?? .unknown)
+        if preferCacheOriginalData {
+            return original ??
+                image.kf.data(
+                    format: original?.kf.imageFormat ?? .unknown,
+                    compressionQuality: compressionQuality
+                )
+        } else {
+            return image.kf.data(
+                format: original?.kf.imageFormat ?? .unknown,
+                compressionQuality: compressionQuality
+            )
+        }
     }
     
     /// Gets an image deserialized from provided data.

+ 9 - 2
Sources/Image/Image.swift

@@ -234,13 +234,20 @@ extension KingfisherWrapper where Base: KFCrossPlatformImage {
     ///
     /// - Parameter format: The format in which the output data should be. If `unknown`, the `base` image will be
     ///                     converted in the PNG representation.
+    ///
     /// - Returns: The output data representing.
-    public func data(format: ImageFormat) -> Data? {
+
+    /// Returns a data representation for `base` image, with the `format` as the format indicator.
+    /// - Parameters:
+    ///   - format: The format in which the output data should be. If `unknown`, the `base` image will be
+    ///   converted in the PNG representation.
+    ///   - compressionQuality: The compression quality when converting image to a lossy format data.
+    public func data(format: ImageFormat, compressionQuality: CGFloat = 1.0) -> Data? {
         return autoreleasepool { () -> Data? in
             let data: Data?
             switch format {
             case .PNG: data = pngRepresentation()
-            case .JPEG: data = jpegRepresentation(compressionQuality: 1.0)
+            case .JPEG: data = jpegRepresentation(compressionQuality: compressionQuality)
             case .GIF: data = gifRepresentation()
             case .unknown: data = normalized.kf.pngRepresentation()
             }

+ 51 - 0
Tests/KingfisherTests/KingfisherManagerTests.swift

@@ -917,6 +917,57 @@ class KingfisherManagerTests: XCTestCase {
         coordinator.apply(.cachingOriginalImage) { called = true }
         XCTAssertEqual(coordinator.state, .done)
     }
+
+    func testCanUseCustomizeDefaultCacheSerializer() {
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+
+        var cacheSerializer = DefaultCacheSerializer()
+        cacheSerializer.preferCacheOriginalData = true
+
+        manager.cache.store(
+            testImage,
+            original: testImageData,
+            forKey: url.cacheKey,
+            processorIdentifier: DefaultImageProcessor.default.identifier,
+            cacheSerializer: cacheSerializer, toDisk: true) {
+                result in
+
+                let computedKey = url.cacheKey.computedKey(with: DefaultImageProcessor.default.identifier)
+                let fileURL = self.manager.cache.diskStorage.cacheFileURL(forKey: computedKey)
+                let data = try! Data(contentsOf: fileURL)
+                XCTAssertEqual(data, testImageData)
+
+                exp.fulfill()
+            }
+        waitForExpectations(timeout: 1.0)
+    }
+
+    func testCanUseCustomizeDefaultCacheSerializerStoreEncoded() {
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+
+        var cacheSerializer = DefaultCacheSerializer()
+        cacheSerializer.compressionQuality = 0.8
+
+        manager.cache.store(
+            testImage,
+            original: testImageJEPGData,
+            forKey: url.cacheKey,
+            processorIdentifier: DefaultImageProcessor.default.identifier,
+            cacheSerializer: cacheSerializer, toDisk: true) {
+                result in
+
+                let computedKey = url.cacheKey.computedKey(with: DefaultImageProcessor.default.identifier)
+                let fileURL = self.manager.cache.diskStorage.cacheFileURL(forKey: computedKey)
+                let data = try! Data(contentsOf: fileURL)
+                XCTAssertNotEqual(data, testImageJEPGData)
+                XCTAssertEqual(data, testImage.kf.jpegRepresentation(compressionQuality: 0.8))
+
+                exp.fulfill()
+            }
+        waitForExpectations(timeout: 1.0)
+    }
 }
 
 class SimpleProcessor: ImageProcessor {