首页 > 代码库 > SDWebImage源码阅读(三)UIImage+GIF

SDWebImage源码阅读(三)UIImage+GIF

  UIImage+GIF 是UIImage 类的一个GIF 分类,在之前的版本里面这个分类是用了处理GIF 动态图片的但是会有内存暴增的bug。在当前 ‘4.0.0-beta2‘ 的版本里GIF 动态图片处理放在了UIImage+MultiFormat  这个分类里面,而当前这个GIF 的分类的功能只是将动态图片作为静态图片来处理,如果是静态图片的NSData 数据则转化为静态UIImage 直接返回,如果是动态图片的NSData 数据,则把图像的第1帧图像转换化为静态UIImage 返回。

  首先看UIImage+GIF.h 文件:

 1 @interface UIImage (GIF)
 2 
 3 /**
 4  *  Compatibility method - creates an animated UIImage from an NSData, it will only contain the 1st frame image
 5  */
 6 + (UIImage *)sd_animatedGIFWithData:(NSData *)data;
 7 
 8 /**
 9  *  Checks if an UIImage instance is a GIF. Will use the `images` array
10  */
11 - (BOOL)isGIF;
12 
13 @end

  定义了一个实例方法和一个类方法:

1 + (UIImage *)sd_animatedGIFWithData:(NSData *)data;

   兼容的方法创建一个动画UIImage 从一个NSData,它仅仅只包含第一帧图像。

  判断一个UIImage 实例是否是GIF 图片:

1 - (BOOL)isGIF;
1 - (BOOL)isGIF {
2     return (self.images != nil);
3 }

  检查一个UIImage 的实例是否是GIF,将使用“images” 数组判断。

  UIImage+GIF.m 文件:

  主要研究上面类方法的实现。

  学习研究之前先做一些拓展:

   size_t 类型:

   size_t 类型定义在cstddef 头文件中,该文件是C标准库的头文件stddef.h 的C++ 版。它是一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小。

  例如:bitset 的size 操作返回bitset对象中二进制位中的个数,返回值类型是size_t。

  例如:在用下标访问元素时,vector使用vector:size_type 作为下标类型,而数组下标的正确类型则是size_t。vector 使用的下标实际也是size_t,源码是: typedef size_t size_type。
  来源:

  size_t 是标准C库中定义的,应为unsigned int,在64位系统中为 long unsigned int。

  使用:

  一个基本的无符号整数的 C/C ++ 类型,它是sizeof 操作符返回的结果类型,该类型的大小可选择。因此,它可以存储在理论上是可能的任何类型的数组的最大大小。换句话说,一个指针可以被安全地放进size_t 类型(一个例外是类的函数指针,但是这是一个特殊的情况)。
  size_t 类型通常用于循环、数组索引、大小的存储和地址运算。 虽然size_t 可以存储一个指针,它的目的是更好地使用另一个unsinged 整数类型uintptr_t 形式。在某些情况下,使用size_t 类型是更为有效,比习惯性使用无符号类型的程序会更安全。
  size_t是在基于无符号整数memsize 类型的 C/C++ 的标准库中定义的。C语言中,此类型位于头文件stddef.h 中,而在C++ 中,则位于cstddef.h 中。

  实现方式:

  在C++ 中,设计 size_t 就是为了适应多个平台的。size_t 的引入增强了程序在不同平台上的可移植性。

  size_t 是针对系统定制的一种数据类型,一般是整型,因为 C/C++ 标准只定义最低的位数,而不是必需的固定位数。而且在内存里,对数的高位对齐存储还是低位对齐存储各系统都不一样。为了提高代码的可移植性,就有必要定义这样的数据类型。一般这种类型都会定义到它具体占几位内存等。当然,有些是编译器或系统已经给定义好的。

  经测试发现,在32位系统中size_t是4字节的,而在64位系统中,size_t 是8字节的,这样利用该类型可以增强程序的可移植性。

  详细解释:

  size_t 在C语言中就有了。
  它是一种“整型”类型,里面保存的是一个整数,就像int, long那样。这种整数用来记录一个大小(size),size_t 的全称应该是size type,就是说“一种用来记录大小的数据类型”。
  用sizeof(xxx) 操作可获取 xxx 占用的内存空间字节数,sizeof 返回的必定是无符号整形,在标准c中通过 typedef 将返回值类型定义为size_t。strlen 获取字符数组实际使用的字节数,不包含数组结尾符’\0’,返回类型size_t。
  若用printf 输出size_t 类型时,C99 中定义格式符 %zd,若编译器不支持可以尝试%u 或%lu 。
  因为size_t类型的数据其实是保存了一个整数,所以它也可以做加减乘除,也可以转化为int并赋值给int类型的变量。
  类似的还有wchar_t, ptrdiff_t。wchar_t就是wide char type,“一种用来记录一个宽字符的数据类型”。ptrdiff_t就是pointer difference type,“一种用来记录两个指针之间的距离的数据类型”。
  通常,size_t 和ptrdiff_t 都是用typedef 来实现的。你可能在某个头文件里面找到类似的语句:
typedef unsigned int size_t; 而wchar_t则稍有不同。在一些旧的编译器中,wchar_t也可能是用typedef来实现,但是新的标准中wchar_t已经是 C/C++ 语言的关键字,wchar_t 类型的地位已经和char, int的地位等同了。
  在标准C/C++的语法中,只有int float char bool等基本的数据类型,至于size_t 或size_type 都是以后的编程人员为了方便记忆所定义的一些便于理解的由基本数据类型的变体类型。
  例如:typedef int size_t; 定义了size_t为整型。

  在编译的过程中size_t类型的a 值会被编译他的补码。所以在使用size_t 类型数据的过程中尤其要注意,特别是在逻辑表达式中使用到该类型,稍不注意可能带来很严重的后果。 
  注:正数的补码:与原码相同;负数的补码:符号位为1,其余位为该数绝对值的原码按位取反,然后整个数加1。

 参考链接:http://blog.csdn.net/JIEJINQUANIL/article/details/50981834

http://jeremybai.github.io/blog/2014/09/10/size-t

http://baike.baidu.com/link?url=WhKafqypjk2GpAfTxbeWLNEQIqW3bjODmV5faNTtZUcPPCn1Y7MDOa05UR4DOstvaVcbGeOXqUcKCRJ6eIz4ba

http://blog.csdn.net/zhanghaotian2011/article/details/7974891

http://www.cnblogs.com/kaituorensheng/p/3239446.html

http://blog.csdn.net/bigloomy/article/details/6563870

  下面看 + (UIImage *)sd_animatedGIFWithData:(NSData *)data; 方法实现:

 1 + (UIImage *)sd_animatedGIFWithData:(NSData *)data {
 2     if (!data) {
 3         return nil;
 4     }
 5 
 6     CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
 7 
 8     size_t count = CGImageSourceGetCount(source);
 9 
10     UIImage *staticImage;
11 
12     if (count <= 1) {
13         staticImage = [[UIImage alloc] initWithData:data];
14     } else {
15         // we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category.
16         // this here is only code to allow drawing animated images as static ones
17 #if SD_WATCH
18         CGFloat scale = 1;
19         scale = [WKInterfaceDevice currentDevice].screenScale;
20 #elif SD_UIKIT
21         CGFloat scale = 1;
22         scale = [UIScreen mainScreen].scale;
23 #endif
24         
25         CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL);
26 #if SD_UIKIT || SD_WATCH
27         UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];
28         staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];
29 #elif SD_MAC
30         staticImage = [[UIImage alloc] initWithCGImage:CGImage size:NSZeroSize];
31 #endif
32         CGImageRelease(CGImage);
33     }
34 
35     CFRelease(source);
36 
37     return staticImage;
38 }

  这里主要使用了 ImageIO 框架下的 <ImageIO/CGImageSource.h>,并在 UIImage+GIF.m 文件开始通过 #import <ImageIO/ImageIO.h>,引入ImageIO 框架。

  1.判断传入的data 如果是nil 则直接返回nil。

  2.使用CGImageSource.h 的:CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL),创建一个CGImageSourceRef 对象source。

1 /* Create an image source reading from `data‘.  The `options‘ dictionary
2  * may be used to request additional creation options; see the list of keys
3  * above for more information. */
4 
5 IMAGEIO_EXTERN CGImageSourceRef __nullable CGImageSourceCreateWithData(CFDataRef __nonnull data, CFDictionaryRef __nullable options) IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);

  3.使用CGImageSource.h 的:CGImageSourceGetCount(source),获得source 里面的图片数量count。

1 /* Return the number of images (not including thumbnails) in the image
2  * source `isrc‘. */
3 
4 IMAGEIO_EXTERN size_t CGImageSourceGetCount(CGImageSourceRef __nonnull isrc)  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);

  4.创建一个UIImage 实例staticImage。(从名字里面能大概猜出创建该实例的用意)

  5.如果count 小于等于1,即表示该data 数据是一个静态图片的NSData 数据,则把data 转化为UIImage 并赋值给staticImage。

  6.如果count 大于1,即表示该data 数据是一个动态图片的NSData 数据。如果当前是WATCH 平台开发通过:

1         CGFloat scale = 1;
2         scale = [WKInterfaceDevice currentDevice].screenScale;

获取当前设备的screenScale,如果是iOS/TV 平台开发(包含UIKit 框架)通过:

1         CGFloat scale = 1;
2         scale = [UIScreen mainScreen].scale;

获得当前屏幕的scale,并赋值给float 类型的scale 变量,用于控制返回的动态图片的第一帧图像的大小。

  7.使用CGImageSource.h 的:CGImageSourceCreateImageAtIndex(source, 0, NULL),获得source 里面index 为0 时的CGImageRef 实例CGImage。

1 /* Return the image at `index‘ in the image source `isrc‘.  The index is
2  * zero-based. The `options‘ dictionary may be used to request additional
3  * creation options; see the list of keys above for more information. */
4 
5 IMAGEIO_EXTERN CGImageRef __nullable CGImageSourceCreateImageAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options)  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);

  8.如果是iOS/TV/WATCH 平台开发,根据上面获取的CGImage 和scale 做参数,使用:

1 + (UIImage *)imageWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(4_0);

创建一个UIImage 实例frameImage。

  9.把frameImage 放在一个NSArray 里面作参数,使用:

1 + (nullable UIImage *)animatedImageWithImages:(NSArray<UIImage *> *)images duration:(NSTimeInterval)duration NS_AVAILABLE_IOS(5_0);

创建一个images 为一张图片,duration 为0.0f的动态图片并赋值给staticImage。如果是MAC 平台开发,依然CGImage 作参数,使用:

1 [[UIImage alloc] initWithCGImage:CGImage size:NSZeroSize];

创建一个UIImage 实例赋值给staticImage。

  10.使用CGImage.h 的CGImageRelease(CGImage) 释放CGImage。

1 /* Equivalent to `CFRelease(image)‘. */
2 
3 CG_EXTERN void CGImageRelease(CGImageRef cg_nullable image)
4     CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

  11.使用CFBase.h 的CFRelease(source)  释放source。

1 CF_EXPORT
2 void CFRelease(CFTypeRef cf);

  12.返回staticImage。

END

参考链接:http://www.jianshu.com/p/d3e9e3d0a778

SDWebImage源码阅读(三)UIImage+GIF