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

Merge branch 'master' from onevcat-master

xspyhack 9 лет назад
Родитель
Сommit
c389809c37

+ 6 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -16,6 +16,8 @@
 		4B3766841C478F940001443F /* Kingfisher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D13F49D61BEDA67C00CE335D /* Kingfisher.framework */; };
 		4B3766A01C4794460001443F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B37669F1C4794460001443F /* CFNetwork.framework */; };
 		4B3766A21C47944D0001443F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B3766A11C47944D0001443F /* CFNetwork.framework */; };
+		4B98674F1CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */; };
+		4B9867501CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */; };
 		B43007AC86DBFFFD1AC6EDD1 /* libPods-KingfisherTests-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 798E024A9311DC80470CF240 /* libPods-KingfisherTests-tvOS.a */; };
 		D10945F71C526B86001408EB /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = D10945EA1C526B6C001408EB /* Image.swift */; };
 		D10945F81C526B86001408EB /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D10945EB1C526B6C001408EB /* ImageCache.swift */; };
@@ -258,6 +260,7 @@
 		4B37669F1C4794460001443F /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.1.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
 		4B3766A11C47944D0001443F /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
 		4B3E714D1B01FEB200F5AAED /* WatchKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WatchKit.framework; path = System/Library/Frameworks/WatchKit.framework; sourceTree = SDKROOT; };
+		4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimatedImageView.swift; path = Sources/AnimatedImageView.swift; sourceTree = "<group>"; };
 		50ECD18204CB0CD37B49F631 /* libPods-KingfisherTests-OSX.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-KingfisherTests-OSX.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		74477D1C4379728A8DA673FB /* Pods-KingfisherTests-OSX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests-OSX.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests-OSX/Pods-KingfisherTests-OSX.debug.xcconfig"; sourceTree = "<group>"; };
 		798E024A9311DC80470CF240 /* libPods-KingfisherTests-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-KingfisherTests-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -440,6 +443,7 @@
 		D10EC22A1C3D62D200A4211C /* Sources */ = {
 			isa = PBXGroup;
 			children = (
+				4B98674E1CD1CF42003ADAC7 /* AnimatedImageView.swift */,
 				D10945EA1C526B6C001408EB /* Image.swift */,
 				D10945EB1C526B6C001408EB /* ImageCache.swift */,
 				D10945EC1C526B6C001408EB /* ImageDownloader.swift */,
@@ -1332,6 +1336,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				D109460E1C526C0D001408EB /* Image.swift in Sources */,
+				4B9867501CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */,
 				D109460F1C526C0D001408EB /* ImageCache.swift in Sources */,
 				D10946101C526C0D001408EB /* ImageDownloader.swift in Sources */,
 				D10946111C526C0D001408EB /* ImageTransition.swift in Sources */,
@@ -1387,6 +1392,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				D10945F71C526B86001408EB /* Image.swift in Sources */,
+				4B98674F1CD1CF42003ADAC7 /* AnimatedImageView.swift in Sources */,
 				D10945F81C526B86001408EB /* ImageCache.swift in Sources */,
 				D10945F91C526B86001408EB /* ImageDownloader.swift in Sources */,
 				D10945FA1C526B86001408EB /* ImageTransition.swift in Sources */,

+ 28 - 15
Sources/AnimatedImageView.swift

@@ -3,25 +3,33 @@
 //  Kingfisher
 //
 //  Created by bl4ckra1sond3tre on 4/22/16.
-//  Copyright © 2016 Wei Wang. All rights reserved.
 //
-//  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 AnimatableImageView, AnimatedFrame and Animator is a modified version of 
+//  some classes from kaishin's Gifu project (https://github.com/kaishin/Gifu)
 //
-//  The above copyright notice and this permission notice shall be included in
-//  all copies or substantial portions of the Software.
+//  The MIT License (MIT)
+//
+//  Copyright (c) 2014-2016 Reda Lemeden.
+//
+//  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.
+//  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.
+//
+//  The name and characters used in the demo of this software are property of their
+//  respective owners.
 
 import UIKit
 import ImageIO
@@ -142,6 +150,11 @@ public class AnimatedImageView: UIImageView {
         didMove()
     }
     
+    // This is for back compatibility that using regular UIImageView to show GIF.
+    override func shouldPreloadAllGIF() -> Bool {
+        return false
+    }
+    
     // MARK: - Private method
     /// Reset the animator.
     private func reset() {

+ 63 - 48
Sources/Image.swift

@@ -85,8 +85,7 @@ extension Image {
         return duration
     }
     
-    #if os(iOS)
-        private(set) var kf_imageSource: ImageSource? {
+    private(set) var kf_imageSource: ImageSource? {
             get {
                 return objc_getAssociatedObject(self, &imageSourceKey) as? ImageSource
             }
@@ -95,7 +94,7 @@ extension Image {
             }
         }
         
-        private(set) var kf_animatedImageData: NSData? {
+    private(set) var kf_animatedImageData: NSData? {
             get {
                 return objc_getAssociatedObject(self, &animatedImageDataKey) as? NSData
             }
@@ -103,7 +102,6 @@ extension Image {
                 objc_setAssociatedObject(self, &animatedImageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
             }
         }
-    #endif
 #endif
 }
 
@@ -190,10 +188,10 @@ func ImageJPEGRepresentation(image: Image, _ compressionQuality: CGFloat) -> NSD
 
 // MARK: - GIF
 func ImageGIFRepresentation(image: Image) -> NSData? {
-#if os(iOS)
-    return image.kf_animatedImageData
-#else
+#if os(OSX)
     return ImageGIFRepresentation(image, duration: 0.0, repeatCount: 0)
+#else
+    return image.kf_animatedImageData
 #endif
 }
 
@@ -227,77 +225,94 @@ func ImagesCountWithImageSource(ref: CGImageSourceRef) -> Int {
 }
 
 extension Image {
-    static func kf_animatedImageWithGIFData(gifData data: NSData) -> Image? {
-        return kf_animatedImageWithGIFData(gifData: data, scale: 1.0, duration: 0.0)
+    static func kf_animatedImageWithGIFData(gifData data: NSData, preloadAll: Bool) -> Image? {
+        return kf_animatedImageWithGIFData(gifData: data, scale: 1.0, duration: 0.0, preloadAll: preloadAll)
     }
     
-    static func kf_animatedImageWithGIFData(gifData data: NSData, scale: CGFloat, duration: NSTimeInterval) -> Image? {
+    static func kf_animatedImageWithGIFData(gifData data: NSData, scale: CGFloat, duration: NSTimeInterval, preloadAll: Bool) -> Image? {
         
+        func decodeFromSource(imageSource: CGImageSource, options: NSDictionary) -> ([Image], NSTimeInterval)? {
+
+            let frameCount = CGImageSourceGetCount(imageSource)
+            var images = [Image]()
+            var gifDuration = 0.0
+            for i in 0 ..< frameCount {
+                
+                guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, options) else {
+                    return nil
+                }
+                
+                if frameCount == 1 {
+                    // Single frame
+                    gifDuration = Double.infinity
+                } else {
+                    // Animated GIF
+                    guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil),
+                        gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary,
+                        frameDuration = (gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber) else
+                    {
+                        return nil
+                    }
+                    gifDuration += frameDuration.doubleValue
+                }
+                
+                images.append(Image.kf_imageWithCGImage(imageRef, scale: scale, refImage: nil))
+            }
+            
+            return (images, gifDuration)
+        }
+        
+        // Start of kf_animatedImageWithGIFData
         let options: NSDictionary = [kCGImageSourceShouldCache as String: NSNumber(bool: true), kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF]
         guard let imageSource = CGImageSourceCreateWithData(data, options) else {
             return nil
         }
-#if os(iOS)
+        
+#if os(OSX)
+        guard let (images, gifDuration) = decodeFromSource(imageSource, options: options) else {
+            return nil
+        }
         let image = Image(data: data)
-        image?.kf_imageSource = ImageSource(ref: imageSource)
-        image?.kf_animatedImageData = data
+        image?.kf_images = images
+        image?.kf_duration = gifDuration
+    
         return image
 #else
-        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 preloadAll {
+            guard let (images, gifDuration) = decodeFromSource(imageSource, options: options) else {
                 return nil
             }
-            
-            if frameCount == 1 {
-                // Single frame
-                gifDuration = Double.infinity
-            } else {
-                // Animated GIF
-                guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil),
-                    gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary,
-                    frameDuration = (gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber) else
-                {
-                    return nil
-                }
-                gifDuration += frameDuration.doubleValue
-            }
-            
-            images.append(Image.kf_imageWithCGImage(imageRef, scale: scale, refImage: nil))
+            let image = Image.kf_animatedImageWithImages(images, duration: duration <= 0.0 ? gifDuration : duration)
+            image?.kf_animatedImageData = data
+            return image
+        } else {
+            let image = Image(data: data)
+            image?.kf_animatedImageData = data
+            image?.kf_imageSource = ImageSource(ref: imageSource)
+            return image
         }
-    #if os(OSX)
-        let image = Image(data: data)
-        image?.kf_images = images
-        image?.kf_duration = gifDuration
-        return image
-    #else
-        return Image.kf_animatedImageWithImages(images, duration: duration <= 0.0 ? gifDuration : duration)
-    #endif
 #endif
+        
     }
 }
 
 // MARK: - Create images from data
 extension Image {
-    static func kf_imageWithData(data: NSData, scale: CGFloat) -> Image? {
+    static func kf_imageWithData(data: NSData, scale: CGFloat, preloadAllGIFData: Bool) -> Image? {
         var image: Image?
         #if os(OSX)
             switch data.kf_imageFormat {
             case .JPEG: image = Image(data: data)
             case .PNG: image = Image(data: data)
-            case .GIF: image = Image.kf_animatedImageWithGIFData(gifData: data, scale: scale, duration: 0.0)
+            case .GIF: image = Image.kf_animatedImageWithGIFData(gifData: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData)
             case .Unknown: image = Image(data: data)
             }
         #else
             switch data.kf_imageFormat {
             case .JPEG: image = Image(data: data, scale: scale)
             case .PNG: image = Image(data: data, scale: scale)
-            case .GIF: image = Image.kf_animatedImageWithGIFData(gifData: data, scale: scale, duration: 0.0)
+            case .GIF: image = Image.kf_animatedImageWithGIFData(gifData: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData)
             case .Unknown: image = Image(data: data, scale: scale)
             }
         #endif

+ 16 - 40
Sources/ImageCache.swift

@@ -144,34 +144,18 @@ public class ImageCache {
 // MARK: - Store & Remove
 extension ImageCache {
     /**
-    Store an image to cache. It will be saved to both memory and disk. 
-    It is an async operation, if you need to do something about the stored image, use `-storeImage:forKey:toDisk:completionHandler:` 
-    instead.
-    
-    - parameter image:        The image will be stored.
-    - parameter originalData: The original data of the image.
-                              Kingfisher will use it to check the format of the image and optimize cache size on disk.
-                              If `nil` is supplied, the image data will be saved as a normalized PNG file.
-                              It is strongly suggested to supply it whenever possible, to get a better performance and disk usage.
-    - parameter key:          Key for the image.
-    */
-    public func storeImage(image: Image, originalData: NSData? = nil, forKey key: String) {
-        storeImage(image, originalData: originalData, forKey: key, toDisk: true, completionHandler: nil)
-    }
-    
-    /**
-    Store an image to cache. It is an async operation.
+    Store an image to cache. It will be saved to both memory and disk. It is an async operation.
     
-    - parameter image:             The image will be stored.
+    - parameter image:             The image to be stored.
     - parameter originalData:      The original data of the image.
                                    Kingfisher will use it to check the format of the image and optimize cache size on disk.
                                    If `nil` is supplied, the image data will be saved as a normalized PNG file. 
                                    It is strongly suggested to supply it whenever possible, to get a better performance and disk usage.
     - parameter key:               Key for the image.
     - parameter toDisk:            Whether this image should be cached to disk or not. If false, the image will be only cached in memory.
-    - parameter completionHandler: Called when stroe operation completes.
+    - parameter completionHandler: Called when store operation completes.
     */
-    public func storeImage(image: Image, originalData: NSData? = nil, forKey key: String, toDisk: Bool, completionHandler: (() -> ())?) {
+    public func storeImage(image: Image, originalData: NSData? = nil, forKey key: String, toDisk: Bool = true, completionHandler: (() -> Void)? = nil) {
         memoryCache.setObject(image, forKey: key, cost: image.kf_imageCost)
         
         func callHandlerInMainQueue() {
@@ -183,7 +167,7 @@ extension ImageCache {
         }
         
         if toDisk {
-            dispatch_async(ioQueue, { () -> Void in
+            dispatch_async(ioQueue, {
                 let imageFormat: ImageFormat
                 if let originalData = originalData {
                     imageFormat = originalData.kf_imageFormat
@@ -216,24 +200,14 @@ extension ImageCache {
     }
     
     /**
-    Remove the image for key for the cache. It will be opted out from both memory and disk.
-    It is an async operation, if you need to do something about the stored image, use `-removeImageForKey:fromDisk:completionHandler:` 
-    instead.
-    
-    - parameter key: Key for the image.
-    */
-    public func removeImageForKey(key: String) {
-        removeImageForKey(key, fromDisk: true, completionHandler: nil)
-    }
-    
-    /**
-    Remove the image for key for the cache. It is an async operation.
+    Remove the image for key for the cache. It will be opted out from both memory and disk. 
+    It is an async operation.
     
     - parameter key:               Key for the image.
     - parameter fromDisk:          Whether this image should be removed from disk or not. If false, the image will be only removed from memory.
     - parameter completionHandler: Called when removal operation completes.
     */
-    public func removeImageForKey(key: String, fromDisk: Bool, completionHandler: (() -> ())?) {
+    public func removeImageForKey(key: String, fromDisk: Bool = true, completionHandler: (() -> Void)? = nil) {
         memoryCache.removeObjectForKey(key)
         
         func callHandlerInMainQueue() {
@@ -286,7 +260,7 @@ extension ImageCache {
             var sSelf: ImageCache! = self
             block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
                 // Begin to load image from disk
-                if let image = sSelf.retrieveImageInDiskCacheForKey(key, scale: options.scaleFactor) {
+                if let image = sSelf.retrieveImageInDiskCacheForKey(key, scale: options.scaleFactor, preloadAllGIFData: options.preloadAllGIFData) {
                     if options.backgroundDecode {
                         dispatch_async(sSelf.processQueue, { () -> Void in
                             let result = image.kf_decodedImage(scale: options.scaleFactor)
@@ -334,12 +308,14 @@ extension ImageCache {
     Get an image for a key from disk.
     
     - parameter key: Key for the image.
-    - param scale: The scale factor to assume when interpreting the image data.
+    - parameter scale: The scale factor to assume when interpreting the image data.
+    - parameter preloadAllGIFData: Whether all GIF data should be loaded. If true, you can set the loaded image to a regular UIImageView to play 
+      the GIF animation. Otherwise, you should use `AnimatedImageView` to play it. Default is `false`
 
     - returns: The image object if it is cached, or `nil` if there is no such key in the cache.
     */
-    public func retrieveImageInDiskCacheForKey(key: String, scale: CGFloat = 1.0) -> Image? {
-        return diskImageForKey(key, scale: scale)
+    public func retrieveImageInDiskCacheForKey(key: String, scale: CGFloat = 1.0, preloadAllGIFData: Bool = false) -> Image? {
+        return diskImageForKey(key, scale: scale, preloadAllGIFData: preloadAllGIFData)
     }
 }
 
@@ -631,9 +607,9 @@ extension ImageCache {
 // MARK: - Internal Helper
 extension ImageCache {
     
-    func diskImageForKey(key: String, scale: CGFloat) -> Image? {
+    func diskImageForKey(key: String, scale: CGFloat, preloadAllGIFData: Bool) -> Image? {
         if let data = diskImageDataForKey(key) {
-            return Image.kf_imageWithData(data, scale: scale)
+            return Image.kf_imageWithData(data, scale: scale, preloadAllGIFData: preloadAllGIFData)
         } else {
             return nil
         }

+ 1 - 1
Sources/ImageDownloader.swift

@@ -449,7 +449,7 @@ class ImageDownloaderSessionHandler: NSObject, NSURLSessionDataDelegate, Authent
             if let fetchLoad = downloader.fetchLoadForKey(URL) {
                 
                 let options = fetchLoad.options ?? KingfisherEmptyOptionsInfo
-                if let image = Image.kf_imageWithData(fetchLoad.responseData, scale: options.scaleFactor) {
+                if let image = Image.kf_imageWithData(fetchLoad.responseData, scale: options.scaleFactor, preloadAllGIFData: options.preloadAllGIFData) {
                     
                     downloader.delegate?.imageDownloader?(downloader, didDownloadImage: image, forURL: URL, withResponse: task.response!)
                     

+ 13 - 1
Sources/ImageView+Kingfisher.swift

@@ -102,7 +102,12 @@ extension ImageView {
         
         kf_setWebURL(resource.downloadURL)
         
-        let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: optionsInfo,
+        var options = optionsInfo ?? []
+        if shouldPreloadAllGIF() {
+            options.append(.PreloadAllGIFData)
+        }
+
+        let task = KingfisherManager.sharedManager.retrieveImageWithResource(resource, optionsInfo: options,
             progressBlock: { receivedSize, totalSize in
                 if let progressBlock = progressBlock {
                     progressBlock(receivedSize: receivedSize, totalSize: totalSize)
@@ -135,6 +140,7 @@ extension ImageView {
                                         UIView.transitionWithView(sSelf, duration: transition.duration,
                                             options: [transition.animationOptions, .AllowUserInteraction],
                                             animations: {
+                                                // Set image property in the animation.
                                                 transition.animations?(sSelf, image)
                                             },
                                             completion: { finished in
@@ -157,6 +163,12 @@ extension ImageView {
     }
 }
 
+extension ImageView {
+    func shouldPreloadAllGIF() -> Bool {
+        return true
+    }
+}
+
 extension ImageView {
     /**
      Cancel the image download task bounded to the image view if it is running.

+ 17 - 9
Sources/KingfisherOptionsInfo.swift

@@ -49,6 +49,7 @@ Items could be added into KingfisherOptionsInfo.
 - BackgroundDecode: Decode the image in background thread before using.
 - CallbackDispatchQueue: The associated value of this member will be used as the target queue of dispatch callbacks when retrieving images from cache. If not set, `Kingfisher` will use main quese for callbacks.
 - ScaleFactor: The associated value of this member will be used as the scale factor when converting retrieved data to an image.
+- PreloadAllGIFData: Whether all the GIF data should be preloaded. Default it false, which means following frames will be loaded on need. If true, all the GIF data will be loaded and decoded into memory. This option is mainly used for back compatibility internally. You should not set it directly. `AnimatedImageView` will not preload all data, while a normal image view (`UIImageView` or `NSImageView`) will load all data. Choose to use corresponding image view type instead of setting this option.
 */
 public enum KingfisherOptionsInfoItem {
     case TargetCache(ImageCache?)
@@ -60,6 +61,7 @@ public enum KingfisherOptionsInfoItem {
     case BackgroundDecode
     case CallbackDispatchQueue(dispatch_queue_t?)
     case ScaleFactor(CGFloat)
+    case PreloadAllGIFData
 }
 
 infix operator <== {
@@ -70,15 +72,17 @@ infix operator <== {
 // This operator returns true if two `KingfisherOptionsInfoItem` enum is the same, without considering the associated values.
 func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Bool {
     switch (lhs, rhs) {
-    case (.TargetCache(_), .TargetCache(_)): return true
-    case (.Downloader(_), .Downloader(_)): return true
-    case (.Transition(_), .Transition(_)): return true
-    case (.DownloadPriority(_), .DownloadPriority(_)): return true
-    case (.ForceRefresh, .ForceRefresh): return true
-    case (.CacheMemoryOnly, .CacheMemoryOnly): return true
-    case (.BackgroundDecode, .BackgroundDecode): return true
-    case (.CallbackDispatchQueue(_), .CallbackDispatchQueue(_)): return true
-    case (.ScaleFactor(_), .ScaleFactor(_)): return true
+    case (.TargetCache(_), .TargetCache(_)): fallthrough
+    case (.Downloader(_), .Downloader(_)): fallthrough
+    case (.Transition(_), .Transition(_)): fallthrough
+    case (.DownloadPriority(_), .DownloadPriority(_)): fallthrough
+    case (.ForceRefresh, .ForceRefresh): fallthrough
+    case (.CacheMemoryOnly, .CacheMemoryOnly): fallthrough
+    case (.BackgroundDecode, .BackgroundDecode): fallthrough
+    case (.CallbackDispatchQueue(_), .CallbackDispatchQueue(_)): fallthrough
+    case (.ScaleFactor(_), .ScaleFactor(_)): fallthrough
+    case (.PreloadAllGIFData, .PreloadAllGIFData): return true
+        
     default: return false
     }
 }
@@ -142,6 +146,10 @@ extension CollectionType where Generator.Element == KingfisherOptionsInfoItem {
         return contains{ $0 <== .BackgroundDecode }
     }
     
+    var preloadAllGIFData: Bool {
+        return contains { $0 <== .PreloadAllGIFData }
+    }
+    
     var callbackDispatchQueue: dispatch_queue_t {
         if let item = kf_firstMatchIgnoringAssociatedValue(.CallbackDispatchQueue(nil)),
             case .CallbackDispatchQueue(let queue) = item

+ 19 - 12
Tests/KingfisherTests/ImageExtensionTests.swift

@@ -56,9 +56,9 @@ class ImageExtensionTests: XCTestCase {
     }
     
     func testGenerateGIFImage() {
-        let image = Image.kf_animatedImageWithGIFData(gifData: testImageGIFData)
+        let image = Image.kf_animatedImageWithGIFData(gifData: testImageGIFData, preloadAll: false)
         XCTAssertNotNil(image, "The image should be initiated.")
-#if os(iOS)
+#if os(iOS) || os(tvOS)
         let count = ImagesCountWithImageSource(image!.kf_imageSource!.imageRef!)
         XCTAssertEqual(count, 8, "There should be 8 frames.")
 #else
@@ -69,25 +69,22 @@ class ImageExtensionTests: XCTestCase {
     }
     
     func testGIFRepresentation() {
-        let image = Image.kf_animatedImageWithGIFData(gifData: testImageGIFData)!
+        let image = Image.kf_animatedImageWithGIFData(gifData: testImageGIFData, preloadAll: false)!
         let data = ImageGIFRepresentation(image)
         
         XCTAssertNotNil(data, "Data should not be nil")
         XCTAssertEqual(data?.kf_imageFormat, ImageFormat.GIF)
         
-        let image1 = Image.kf_animatedImageWithGIFData(gifData: data!)!
-#if os(iOS)
-        XCTAssertNotNil(image1.kf_imageSource!.imageRef, "Image source should not be nil")
-#else
-        XCTAssertEqual(image1.kf_duration, image.kf_duration)
-        XCTAssertEqual(image1.kf_images!.count, image.kf_images!.count)
-#endif
+        let allLoadImage = Image.kf_animatedImageWithGIFData(gifData: data!, preloadAll: true)!
+        let allLoadData = ImageGIFRepresentation(allLoadImage)
+        XCTAssertNotNil(allLoadData, "Data1 should not be nil")
+        XCTAssertEqual(allLoadData?.kf_imageFormat, ImageFormat.GIF)
     }
     
     func testGenerateSingleFrameGIFImage() {
-        let image = Image.kf_animatedImageWithGIFData(gifData: testImageSingleFrameGIFData)
+        let image = Image.kf_animatedImageWithGIFData(gifData: testImageSingleFrameGIFData, preloadAll: false)
         XCTAssertNotNil(image, "The image should be initiated.")
-#if os(iOS)
+#if os(iOS) || os(tvOS)
         let count = ImagesCountWithImageSource(image!.kf_imageSource!.imageRef!)
         XCTAssertEqual(count, 1, "There should be 1 frames.")
 #else
@@ -96,4 +93,14 @@ class ImageExtensionTests: XCTestCase {
         XCTAssertEqual(image!.kf_duration, Double.infinity, "The image duration should be 0 since it is not animated image.")
 #endif
     }
+    
+    func testPreloadAllGIFData() {
+        let image = Image.kf_animatedImageWithGIFData(gifData: testImageSingleFrameGIFData, preloadAll: true)!
+        XCTAssertNotNil(image, "The image should be initiated.")
+#if os(iOS) || os(tvOS)
+        XCTAssertNil(image.kf_imageSource, "Image source should be nil")
+#endif
+        XCTAssertEqual(image.kf_duration, image.kf_duration)
+        XCTAssertEqual(image.kf_images!.count, image.kf_images!.count)
+    }
 }

+ 10 - 0
codecov.yml

@@ -0,0 +1,10 @@
+comment:
+  layout: header, changes, diff
+coverage:
+  ignore:
+  - Pods
+  - Tests
+  - Demo
+  - Kingfisher-Demo
+  - KingfisherTests
+