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

Refactor for animated image creating

onevcat пре 7 година
родитељ
комит
3bde20c79b

+ 10 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -114,6 +114,10 @@
 		D12E0C871C47F7AF00AC98AD /* KingfisherOptionsInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12E0C4B1C47F23500AC98AD /* KingfisherOptionsInfoTests.swift */; };
 		D12E0C891C47F7B700AC98AD /* KingfisherTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D12E0C4C1C47F23500AC98AD /* KingfisherTestHelper.swift */; };
 		D12E0C8A1C47F7C000AC98AD /* dancing-banana.gif in Resources */ = {isa = PBXBuildFile; fileRef = D12E0C441C47F23500AC98AD /* dancing-banana.gif */; };
+		D1376B9A215BC66400FCEF3A /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1376B99215BC66400FCEF3A /* KingfisherError.swift */; };
+		D1376B9B215BC66400FCEF3A /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1376B99215BC66400FCEF3A /* KingfisherError.swift */; };
+		D1376B9C215BC66400FCEF3A /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1376B99215BC66400FCEF3A /* KingfisherError.swift */; };
+		D1376B9D215BC66400FCEF3A /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1376B99215BC66400FCEF3A /* KingfisherError.swift */; };
 		D13EA67D205C189C004F625F /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13EA67C205C189C004F625F /* Box.swift */; };
 		D13EA67E205C189C004F625F /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13EA67C205C189C004F625F /* Box.swift */; };
 		D13EA67F205C189C004F625F /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13EA67C205C189C004F625F /* Box.swift */; };
@@ -222,6 +226,7 @@
 		D12E0C4D1C47F23500AC98AD /* KingfisherTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KingfisherTests-Bridging-Header.h"; sourceTree = "<group>"; };
 		D12E0C4E1C47F23500AC98AD /* UIButtonExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIButtonExtensionTests.swift; sourceTree = "<group>"; };
 		D12E0C5F1C47F24800AC98AD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		D1376B99215BC66400FCEF3A /* KingfisherError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = KingfisherError.swift; path = Sources/KingfisherError.swift; sourceTree = "<group>"; };
 		D13EA67C205C189C004F625F /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Box.swift; path = Sources/Box.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; };
@@ -329,6 +334,7 @@
 				444C03E01F547AAE00990BCC /* Placeholder.swift */,
 				D10945F31C526B6C001408EB /* Resource.swift */,
 				4BD8E04B1D9237E200A091BE /* Kingfisher.swift */,
+				D1376B99215BC66400FCEF3A /* KingfisherError.swift */,
 			);
 			name = Core;
 			sourceTree = "<group>";
@@ -1061,6 +1067,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				D109461A1C526C61001408EB /* Image.swift in Sources */,
+				D1376B9C215BC66400FCEF3A /* KingfisherError.swift in Sources */,
 				D109461B1C526C61001408EB /* ImageCache.swift in Sources */,
 				D109461C1C526C61001408EB /* ImageDownloader.swift in Sources */,
 				D109461D1C526C61001408EB /* ImageTransition.swift in Sources */,
@@ -1145,6 +1152,7 @@
 				4BB24C3E1D79215A00CD5F9C /* CacheSerializer.swift in Sources */,
 				D10946171C526C0D001408EB /* ThreadHelper.swift in Sources */,
 				444C03EA1F548F6200990BCC /* Placeholder.swift in Sources */,
+				D1376B9B215BC66400FCEF3A /* KingfisherError.swift in Sources */,
 				F78F5EBE1FCDDE42001A9111 /* ImageModifier.swift in Sources */,
 				FB402D0F1EDEAB7E002B62A1 /* FormatIndicatedCacheSerializer.swift in Sources */,
 				D10946181C526C0D001408EB /* UIButton+Kingfisher.swift in Sources */,
@@ -1173,6 +1181,7 @@
 				D109462A1C526CE8001408EB /* Resource.swift in Sources */,
 				D109462B1C526CE8001408EB /* String+MD5.swift in Sources */,
 				D109462C1C526CE8001408EB /* ThreadHelper.swift in Sources */,
+				D1376B9D215BC66400FCEF3A /* KingfisherError.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1200,6 +1209,7 @@
 				4BB24C3D1D79215A00CD5F9C /* CacheSerializer.swift in Sources */,
 				D10946001C526B86001408EB /* ThreadHelper.swift in Sources */,
 				444C03E91F548F6100990BCC /* Placeholder.swift in Sources */,
+				D1376B9A215BC66400FCEF3A /* KingfisherError.swift in Sources */,
 				F78F5EBD1FCDDE42001A9111 /* ImageModifier.swift in Sources */,
 				FB402D0E1EDEAB7E002B62A1 /* FormatIndicatedCacheSerializer.swift in Sources */,
 				D10946011C526B86001408EB /* UIButton+Kingfisher.swift in Sources */,

+ 32 - 0
Kingfisher.xcworkspace/xcshareddata/IDETemplateMacros.plist

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>FILEHEADER</key>
+	<string>
+//  ___FILENAME___
+//  Kingfisher
+//
+//  Created by ___USERNAME___ on ___DATE___.
+//
+//  Copyright (c) ___YEAR___ Wei Wang &lt;onevcat@gmail.com&gt;
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the &quot;Software&quot;), 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 &quot;AS IS&quot;, 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.</string>
+</dict>
+</plist>

+ 2 - 2
Sources/AnimatedImageView.swift

@@ -224,7 +224,7 @@ open class AnimatedImageView: UIImageView {
     /// Reset the animator.
     private func reset() {
         animator = nil
-        if let imageSource = image?.kf.imageSource?.imageRef {
+        if let imageSource = image?.kf.imageSource {
             animator = Animator(imageSource: imageSource,
                                 contentMode: contentMode,
                                 size: bounds.size,
@@ -469,7 +469,7 @@ class Animator {
     }
 }
 
-extension CGImageSource: KingfisherCompatible { }
+extension CGImageSource: KingfisherClassCompatible { }
 extension KingfisherClass where Base: CGImageSource {
     func gifProperties(at index: Int) -> [String: Double]? {
         let properties = CGImageSourceCopyPropertiesAtIndex(base, index, nil) as Dictionary?

+ 168 - 201
Sources/Image.swift

@@ -34,25 +34,30 @@ import UIKit
 import MobileCoreServices
 private var imageSourceKey: Void?
 #endif
-private var animatedImageDataKey: Void?
-
-import ImageIO
-import CoreGraphics
 
 #if !os(watchOS)
 import Accelerate
 import CoreImage
 #endif
 
+private var animatedImageDataKey: Void?
+
+import ImageIO
+import CoreGraphics
+
+func getAssociatedObject<T>(_ object: Any, _ key: UnsafeRawPointer) -> T? {
+    return objc_getAssociatedObject(object, key) as? T
+}
+
+func setRetainedAssociatedObject<T>(_ object: Any, _ key: UnsafeRawPointer, _ value: T) {
+    objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+}
+
 // MARK: - Image Properties
 extension KingfisherClass where Base: Image {
-    fileprivate(set) var animatedImageData: Data? {
-        get {
-            return objc_getAssociatedObject(base, &animatedImageDataKey) as? Data
-        }
-        set {
-            objc_setAssociatedObject(base, &animatedImageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-        }
+    private(set) var animatedImageData: Data? {
+        get { return getAssociatedObject(base, &animatedImageDataKey) }
+        set { setRetainedAssociatedObject(base, &animatedImageDataKey, newValue) }
     }
     
     #if os(macOS)
@@ -64,58 +69,33 @@ extension KingfisherClass where Base: Image {
         return 1.0
     }
     
-    fileprivate(set) var images: [Image]? {
-        get {
-            return objc_getAssociatedObject(base, &imagesKey) as? [Image]
-        }
-        set {
-            objc_setAssociatedObject(base, &imagesKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-        }
+    private(set) var images: [Image]? {
+        get { return getAssociatedObject(base, &imagesKey) }
+        set { setRetainedAssociatedObject(base, &imagesKey, newValue) }
     }
     
-    fileprivate(set) var duration: TimeInterval {
-        get {
-            return objc_getAssociatedObject(base, &durationKey) as? TimeInterval ?? 0.0
-        }
-        set {
-            objc_setAssociatedObject(base, &durationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-        }
+    private(set) var duration: TimeInterval {
+        get { return getAssociatedObject(base, &durationKey) ?? 0.0 }
+        set { setRetainedAssociatedObject(base, &durationKey, newValue) }
     }
     
     var size: CGSize {
-        return base.representations.reduce(CGSize.zero, { size, rep in
-            return CGSize(width: max(size.width, CGFloat(rep.pixelsWide)), height: max(size.height, CGFloat(rep.pixelsHigh)))
-        })
-    }
-    
-    #else
-    var cgImage: CGImage? {
-        return base.cgImage
-    }
-    
-    var scale: CGFloat {
-        return base.scale
-    }
-    
-    var images: [Image]? {
-        return base.images
-    }
-    
-    var duration: TimeInterval {
-        return base.duration
-    }
-    
-    fileprivate(set) var imageSource: ImageSource? {
-        get {
-            return objc_getAssociatedObject(base, &imageSourceKey) as? ImageSource
-        }
-        set {
-            objc_setAssociatedObject(base, &imageSourceKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+        return base.representations.reduce(.zero) { size, rep in
+            let width = max(size.width, CGFloat(rep.pixelsWide))
+            let height = max(size.height, CGFloat(rep.pixelsHigh))
+            return CGSize(width: width, height: height)
         }
     }
+    #else
+    var cgImage: CGImage? { return base.cgImage }
+    var scale: CGFloat { return base.scale }
+    var images: [Image]? { return base.images }
+    var duration: TimeInterval { return base.duration }
+    var size: CGSize { return base.size }
     
-    var size: CGSize {
-        return base.size
+    private(set) var imageSource: CGImageSource? {
+        get { return getAssociatedObject(base, &imageSourceKey) }
+        set { setRetainedAssociatedObject(base, &imageSourceKey, newValue) }
     }
     #endif
 }
@@ -124,43 +104,29 @@ extension KingfisherClass where Base: Image {
 extension KingfisherClass where Base: Image {
     #if os(macOS)
     static func image(cgImage: CGImage, scale: CGFloat, refImage: Image?) -> Image {
-        return Image(cgImage: cgImage, size: CGSize.zero)
+        return Image(cgImage: cgImage, size: .zero)
     }
     
-    /**
-     Normalize the image. This method does nothing in OS X.
-     
-     - returns: The image itself.
-     */
-    public var normalized: Image {
-        return base
-    }
-    
-    static func animated(with images: [Image], forDuration forDurationduration: TimeInterval) -> Image? {
-        return nil
-    }
+    /// Normalize the image. This getter does nothing on macOS.
+    public var normalized: Image { return base }
+
     #else
+    /// Creating an image from a give `CGImage` at scale and orientation for refImage. The method signature is for
+    /// compability of macOS version.
     static func image(cgImage: CGImage, scale: CGFloat, refImage: Image?) -> Image {
-        if let refImage = refImage {
-            return Image(cgImage: cgImage, scale: scale, orientation: refImage.imageOrientation)
-        } else {
-            return Image(cgImage: cgImage, scale: scale, orientation: .up)
-        }
+        return Image(cgImage: cgImage, scale: scale, orientation: refImage?.imageOrientation ?? .up)
     }
     
-    /**
-     Normalize the image. This method will try to redraw an image with orientation and scale considered.
-     
-     - returns: The normalized image with orientation set to up and correct scale.
-     */
+    /// Normalized image of current `base`.
+    /// This method will try to redraw an image with orientation and scale considered.
     public var normalized: Image {
         // prevent animated image (GIF) lose it's images
         guard images == nil else { return base }
         // No need to do anything if already up
         guard base.imageOrientation != .up else { return base }
     
-        return draw(cgImage: nil, to: size) {
-            base.draw(in: CGRect(origin: CGPoint.zero, size: size))
+        return draw(to: size) {
+            base.draw(in: CGRect(origin: .zero, size: size))
         }
     }
     
@@ -204,94 +170,135 @@ extension KingfisherClass where Base: Image {
     }
 }
 
-// MARK: - Create images from data
-extension KingfisherClass where Base: Image {
-    public static func animated(with data: Data, scale: CGFloat = 1.0, duration: TimeInterval = 0.0, preloadAll: Bool, onlyFirstFrame: Bool = false) -> Image? {
+public struct AnimatedImageCreatingOptions {
+    public let scale: CGFloat
+    public let duration: TimeInterval
+    public let preloadAll: Bool
+    public let onlyFirstFrame: Bool
+    
+    public init(
+        scale: CGFloat = 1.0,
+        duration: TimeInterval = 0.0,
+        preloadAll: Bool = false,
+        onlyFirstFrame: Bool = false)
+    {
+        self.scale = scale
+        self.duration = duration
+        self.preloadAll = preloadAll
+        self.onlyFirstFrame = onlyFirstFrame
+    }
+}
+
+class AnimatedImage {
+    let images: [Image]
+    let duration: TimeInterval
+    
+    init?(from imageSource: CGImageSource, for info: [String: Any], options: AnimatedImageCreatingOptions) {
+        let frameCount = CGImageSourceGetCount(imageSource)
+        var images = [Image]()
+        var gifDuration = 0.0
         
-        func decode(from imageSource: CGImageSource, for options: NSDictionary) -> ([Image], TimeInterval)? {
-            
-            //Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary
-            func frameDuration(from gifInfo: NSDictionary?) -> Double {
-                let gifDefaultFrameDuration = 0.100
-                
-                guard let gifInfo = gifInfo else {
-                    return gifDefaultFrameDuration
-                }
-                
-                let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
-                let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
-                let duration = unclampedDelayTime ?? delayTime
-                
-                guard let frameDuration = duration else { return gifDefaultFrameDuration }
-                
-                return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : gifDefaultFrameDuration
+        for i in 0 ..< frameCount {
+            guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, info as CFDictionary) else {
+                return nil
             }
             
-            let frameCount = CGImageSourceGetCount(imageSource)
-            var images = [Image]()
-            var gifDuration = 0.0
-            for i in 0 ..< frameCount {
-                
-                guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, options) else {
+            if frameCount == 1 {
+                gifDuration = .infinity
+            } else {
+                // Get current animated GIF frame duration
+                guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) else {
                     return nil
                 }
-
-                if frameCount == 1 {
-                    // Single frame
-                    gifDuration = Double.infinity
-                } else {
-                    
-                    // Animated GIF
-                    guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) else {
-                        return nil
-                    }
-
-                    let gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary
-                    gifDuration += frameDuration(from: gifInfo)
-                }
-                
-                images.append(KingfisherClass<Image>.image(cgImage: imageRef, scale: scale, refImage: nil))
                 
-                if onlyFirstFrame { break }
+                let gifInfo = (properties as? [String: Any])?[kCGImagePropertyGIFDictionary as String] as? [String: Any]
+                gifDuration += AnimatedImage.getFrameDuration(from: gifInfo)
             }
-            
-            return (images, gifDuration)
+            images.append(KingfisherClass<Image>.image(cgImage: imageRef, scale: options.scale, refImage: nil))
+            if options.onlyFirstFrame { break }
         }
+        self.images = images
+        self.duration = gifDuration
+    }
+    
+     //Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary.
+    static func getFrameDuration(from gifInfo: [String: Any]?) -> TimeInterval {
+        let defaultFrameDuration = 0.1
+        guard let gifInfo = gifInfo else { return defaultFrameDuration }
+        
+        let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
+        let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
+        let duration = unclampedDelayTime ?? delayTime
+
+        guard let frameDuration = duration else { return defaultFrameDuration }
+        return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : defaultFrameDuration
+    }
+}
+
+// MARK: - Create images from data
+extension KingfisherClass where Base: Image {
+    
+    public static func animated(with data: Data, options: AnimatedImageCreatingOptions) -> Image? {
+        let info: [String: Any] = [
+            kCGImageSourceShouldCache as String: true,
+            kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF
+        ]
         
-        // Start of kf.animatedImageWithGIFData
-        let options: NSDictionary = [kCGImageSourceShouldCache as String: true, kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF]
-        guard let imageSource = CGImageSourceCreateWithData(data as CFData, options) else {
+        guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else {
             return nil
         }
         
         #if os(macOS)
-            guard let (images, gifDuration) = decode(from: imageSource, for: options) else {
-                return nil
-            }
-            let image: Image?
-            if onlyFirstFrame {
-                image = images.first
-            } else {
-                image = Image(data: data)
-                image?.kf.images = images
-                image?.kf.duration = gifDuration
-            }
-            image?.kf.animatedImageData = data
-            return image
+        guard let animatedImage = AnimatedImage(from: imageSource, for: info, options: options) else {
+            return nil
+        }
+        let image: Image?
+        if options.onlyFirstFrame {
+            image = animatedImage.images.first
+        } else {
+            image = Image(data: data)
+            let kf = image?.kf
+            kf?.images = animatedImage.images
+            kf?.duration = animatedImage.duration
+        }
+        image?.kf.animatedImageData = data
+        return image
         #else
-            
-            let image: Image?
-            if preloadAll || onlyFirstFrame {
-                guard let (images, gifDuration) = decode(from: imageSource, for: options) else { return nil }
-                image = onlyFirstFrame ? images.first : Kingfisher<Image>.animated(with: images, forDuration: duration <= 0.0 ? gifDuration : duration)
-            } else {
-                image = Image(data: data)
-                image?.kf.imageSource = ImageSource(ref: imageSource)
+        
+        let image: Image?
+        if options.preloadAll || options.onlyFirstFrame {
+            // Use `images` image if you want to preload all animated data
+            guard let animatedImage = AnimatedImage(from: imageSource, for: info, options: options) else {
+                return nil
             }
+            image = options.onlyFirstFrame ? animatedImage.images.first :
+                KingfisherClass<Image>.animated(
+                    with: animatedImage.images,
+                    forDuration: options.duration <= 0.0 ? animatedImage.duration : options.duration)
             image?.kf.animatedImageData = data
-            return image
+        } else {
+            image = Image(data: data)
+            let kf = image?.kf
+            kf?.imageSource = imageSource
+            kf?.animatedImageData = data
+        }
+        
+        return image
         #endif
     }
+    
+    @available(*, deprecated, message: "Pass parameters with `AnimatedImageCreatingOptions` instead.")
+    public static func animated(
+        with data: Data,
+        scale: CGFloat = 1.0,
+        duration: TimeInterval = 0.0,
+        preloadAll: Bool,
+        onlyFirstFrame: Bool = false) -> Image?
+    {
+        let options = AnimatedImageCreatingOptions(
+            scale: scale, duration: duration, preloadAll: preloadAll, onlyFirstFrame: onlyFirstFrame)
+        return animated(with: data, options: options)
+    }
 
     public static func image(data: Data, scale: CGFloat, preloadAllAnimationData: Bool, onlyFirstFrame: Bool) -> Image? {
         var image: Image?
@@ -303,12 +310,9 @@ extension KingfisherClass where Base: Image {
             case .PNG:
                 image = Image(data: data)
             case .GIF:
-                image = KingfisherClass<Image>.animated(
-                    with: data,
-                    scale: scale,
-                    duration: 0.0,
-                    preloadAll: preloadAllAnimationData,
-                    onlyFirstFrame: onlyFirstFrame)
+                let options = AnimatedImageCreatingOptions(
+                    scale: scale, duration: 0.0, preloadAll: preloadAllAnimationData, onlyFirstFrame: onlyFirstFrame)
+                image = KingfisherClass<Image>.animated(with: data, options: options)
             case .unknown:
                 image = Image(data: data)
             }
@@ -319,12 +323,9 @@ extension KingfisherClass where Base: Image {
             case .PNG:
                 image = Image(data: data, scale: scale)
             case .GIF:
-                image = Kingfisher<Image>.animated(
-                    with: data,
-                    scale: scale,
-                    duration: 0.0,
-                    preloadAll: preloadAllAnimationData,
-                    onlyFirstFrame: onlyFirstFrame)
+                let options = AnimatedImageCreatingOptions(
+                    scale: scale, duration: 0.0, preloadAll: preloadAllAnimationData, onlyFirstFrame: onlyFirstFrame)
+                image = KingfisherClass<Image>.animated(with: data, options: options)
             case .unknown:
                 image = Image(data: data, scale: scale)
             }
@@ -740,14 +741,6 @@ extension KingfisherClass where Base: Image {
     }
 }
 
-/// Reference the source image reference
-final class ImageSource {
-    var imageRef: CGImageSource?
-    init(ref: CGImageSource) {
-        self.imageRef = ref
-    }
-}
-
 // MARK: - Image format
 private struct ImageHeaderData {
     static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
@@ -762,21 +755,8 @@ public enum ImageFormat {
 
 
 // MARK: - Misc Helpers
-public struct DataProxy {
-    fileprivate let base: Data
-    init(proxy: Data) {
-        base = proxy
-    }
-}
-
-extension Data: KingfisherStructCompatible {
-    public typealias CompatibleType = DataProxy
-    public var kf: DataProxy {
-        return DataProxy(proxy: self)
-    }
-}
-
-extension DataProxy {
+extension Data: KingfisherStructCompatible {}
+extension KingfisherStruct where Base == Data {
     public var imageFormat: ImageFormat {
         var buffer = [UInt8](repeating: 0, count: 8)
         (base as NSData).getBytes(&buffer, length: 8)
@@ -798,22 +778,9 @@ extension DataProxy {
     }
 }
 
-public struct CGSizeProxy {
-    fileprivate let base: CGSize
-    init(proxy: CGSize) {
-        base = proxy
-    }
-}
-
-extension CGSize: KingfisherStructCompatible {
-    public typealias CompatibleType = CGSizeProxy
-    public var kf: CGSizeProxy {
-        return CGSizeProxy(proxy: self)
-    }
-}
+extension CGSize: KingfisherStructCompatible {}
+extension KingfisherStruct where Base == CGSize {
 
-extension CGSizeProxy {
-    
     public func resize(to size: CGSize, for contentMode: ContentMode) -> CGSize {
         switch contentMode {
         case .aspectFit:
@@ -916,7 +883,7 @@ extension KingfisherClass where Base: Image {
         #endif
     }
     
-    func draw(cgImage: CGImage?, to size: CGSize, draw: ()->()) -> Image {
+    func draw(cgImage: CGImage? = nil, to size: CGSize, draw: ()->()) -> Image {
         #if os(macOS)
         guard let rep = NSBitmapImageRep(
             bitmapDataPlanes: nil,

+ 2 - 2
Sources/ImageCache.swift

@@ -536,7 +536,7 @@ open class ImageCache {
     */
     @objc public func backgroundCleanExpiredDiskCache() {
         // if 'sharedApplication()' is unavailable, then return
-        guard let sharedApplication = Kingfisher<UIApplication>.shared else { return }
+        guard let sharedApplication = KingfisherClass<UIApplication>.shared else { return }
 
         func endBackgroundTask(_ task: inout UIBackgroundTaskIdentifier) {
             sharedApplication.endBackgroundTask(task)
@@ -706,7 +706,7 @@ extension Dictionary {
 
 #if !os(macOS) && !os(watchOS)
 // MARK: - For App Extensions
-extension UIApplication: KingfisherCompatible { }
+extension UIApplication: KingfisherClassCompatible { }
 extension KingfisherClass where Base: UIApplication {
     public static var shared: UIApplication? {
         let selector = NSSelectorFromString("sharedApplication")

+ 1 - 1
Sources/Kingfisher.swift

@@ -88,5 +88,5 @@ extension Image: KingfisherClassCompatible { }
 extension ImageView: KingfisherClassCompatible { }
 extension Button: KingfisherClassCompatible { }
 #else
-extension WKInterfaceImage: KingfisherCompatible { }
+extension WKInterfaceImage: KingfisherClassCompatible { }
 #endif

+ 30 - 0
Sources/KingfisherError.swift

@@ -0,0 +1,30 @@
+//
+//  KingfisherError.swift
+//  Kingfisher
+//
+//  Created by onevcat on 2018/09/26.
+//
+//  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
+
+struct KingfisherPlaceholderError: Error {
+}

+ 3 - 3
Tests/KingfisherTests/ImageExtensionTests.swift

@@ -61,7 +61,7 @@ class ImageExtensionTests: XCTestCase {
         let image = KingfisherClass<Image>.animated(with: testImageGIFData, preloadAll: false)
         XCTAssertNotNil(image, "The image should be initiated.")
 #if os(iOS) || os(tvOS)
-        let count = CGImageSourceGetCount(image!.kf.imageSource!.imageRef!)
+        let count = CGImageSourceGetCount(image!.kf.imageSource!)
         XCTAssertEqual(count, 8, "There should be 8 frames.")
 #else
         XCTAssertEqual(image!.kf.images!.count, 8, "There should be 8 frames.")
@@ -86,7 +86,7 @@ class ImageExtensionTests: XCTestCase {
         let image = KingfisherClass<Image>.animated(with: testImageSingleFrameGIFData, preloadAll: false)
         XCTAssertNotNil(image, "The image should be initiated.")
 #if os(iOS) || os(tvOS)
-        let count = CGImageSourceGetCount(image!.kf.imageSource!.imageRef!)
+        let count = CGImageSourceGetCount(image!.kf.imageSource!)
         XCTAssertEqual(count, 1, "There should be 1 frames.")
 #else
         XCTAssertEqual(image!.kf.images!.count, 1, "There should be 1 frames.")
@@ -190,7 +190,7 @@ class ImageExtensionTests: XCTestCase {
         XCTAssertEqual(image.size, CGSize(width: 64, height: 64))
         XCTAssertEqual(image.scale, 1.0)
 
-        let image_2x = Kingfisher<Image>.image(cgImage: image.cgImage!, scale: 2.0, refImage: image)
+        let image_2x = KingfisherClass<Image>.image(cgImage: image.cgImage!, scale: 2.0, refImage: image)
         XCTAssertEqual(image_2x.size, CGSize(width: 32, height: 32))
         XCTAssertEqual(image_2x.scale, 2.0)