品读优秀的第三方框架是最快的技术提升方式吧,在工作中,很多时候在思考如何封装一个功能组件时总会各种犹豫,为打破这种顾此失彼的状态,果断看一些源码,学习别人编程思想,并从中借鉴模仿其手法。 花了一周看这个框架顺便写个总结吧! 
SDWebImage 源代码结构
SDWebImage 主要功能就是图片的异步下载+缓存,其次也包含了图片的解码和清除等;源码下载地址 SDWebImage Github 上源码(下载最新的 3.7.0 版本),下载后打开工程代码文件结构还是很清晰的,从文件命名初步分析整体如下图:

再从我们使用 SDWebImage 调用 API 入口入手一步步看调用到的方法和每个文件每个类,主要解析分为以下几类:
WebCache Categories 面向开发者接口层,开发者直接调用 API 接口异步加载图片,也就是说这是我们读代码的入口,入口方法中会调用管理类(协调调用下载和缓存等),例如 UIImageView+WebCache 类中的:
1 2
   | - (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
 
   | 
 
 
SDWebImageManager 是 Utils 管理工具类文件中,其是整个 SDWebImage 的核心负责发起下载和缓存,执行上步骤的方法后,上步骤方法会调用这个类的如下方法,在方法中调用下载和缓存的类;
1 2 3 4 5 6 7 8 9 10
   |  #import "SDWebImageOperation.h" #import "SDWebImageDownloader.h" #import "SDImageCache.h"
  - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url                                         options:(SDWebImageOptions)options                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock; 
 
 
  | 
 
 
SDImageCache 缓存对象,负责内存缓存和沙盒缓存的类,核心方法:
1 2
   |  - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
 
  | 
 
 
SDWebImageDownloader 下载器也就是下载管理器,下载网络请求的前期设置 NSURLRequest对象请求头的封装、缓存、cookie的设置加载选项的处理及管理 Operation 之间的关系;SDWebImageDownloaderOperation 负责图片下载网络请求的具体操作;
1 2 3 4 5 6 7 8 9 10 11
   |  - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url                                         options:(SDWebImageDownloaderOptions)options                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock                                       completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
 
  - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection;
 
 
  | 
 
 
运用到的其它工具类主要是:SDWebImageCompat 、SDWebImageDecoder、NSData+ImageContentType、UIImage+MultiFormat、UIImage+GIF、UIImage+WebP;
 
内部方法逻辑流程分析
SDWebImage 实现图片的加载、缓存处理、数据处理、图片下载等工作内部方法时序图:

- 创建 UIImageView 对象调用其分类 UIImageView+WebCache 的接口方法例如:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder (常用方法还有其它接口方法);  
- UIImageView+WebCache 内部最终都会调用方法 (private method) 
sd_setImageWithURL: placeholderImage: options: progress: completed:; 
- 上步方法内部实现首先判断需不需要取消当前图片的下载操作(cell 复用一个 UIImageView 多个下载会出现闪图),其调用的核心方法是 UIView+WebCacheOperation 
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key,这里思考为什么设计为 UIView 的分类,很好理解因为 UIButton 也要调用, 他们都是继承 UIView,其次调用者都是 self 对象本身; 
- 第二步中内部方法中执行完第三步逻辑(判断完是否取消当前图片下载操作)后,继续执行,调用负责发起下载和缓存 SDWebImageManager 中的方法 
downloadImageWithURL: options: progress: completed: ; 
- SDWebImageManager 对象的上述方法执行过程中通过调用 SDImageCache 对象的 
queryDiskCacheForKey: done: 方法去查询缓存相关操作,SDImageCache 对象活动完成后将结果返回; 
- SDWebImageManager 对象的方法执行完判断缓存相关操作,继续执行调用 SDWebImageDownloader (下载器)对象的 
downloadImageWithURL: options: progress: completed: 发起下载操作;具体的图片网络下载是由 SDWebImageDownloader 对象调用 SDWebImageDownloaderOperation 完成; 
- 图片下载完成后图片结果 block 回调到 SDWebImageManager 对象中,SDWebImageManager 继续调用 SDImageCache 对象执行图片存储操作;
 
核心工具类分析
1. SDWebImageCompat 适配类
只有一个内联函数判断 @2x 和 @3x 图片判断处理操作, 核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | 
 
 
 
 
 
  inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image) {          if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {         CGFloat scale = 1;                  if (key.length >= 8) {             NSRange range = [key rangeOfString:@"@2x."];             if (range.location != NSNotFound) {                 scale = 2.0;             }                          range = [key rangeOfString:@"@3x."];             if (range.location != NSNotFound) {                 scale = 3.0;             }         }
                   UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];         image = scaledImage;     }
     return image; }
 
  | 
 
2. SDWebImageDecoder 解码
UIImage 的分类扩展一个类方法用于图片的解码操作,这里疑问是本来就是图片为何还要解码到 UIImage?图片的加载常使用 [UIImage imageNamed:@"xxx.png"],这种方式加载系统会把图像 Cache 到内存并且系统默认会在主线程对图片进行解码(系统自行完成 可看看 Core Graphics 框架),图像资源大或者图片多,这种方式会消耗很大的内存;解决这种问题可以使用 imageWithContentsOfFile: 图片加载方式,也可以将图片解码过程我们来把控提前在子线程中完成;
UIImage 是显示图片的高级方式,是不可变的,因而可安全的供线程使用;CGImage 是图片位图,是可变的,可调用 Core Graphics 层 API 改变 bitmaps 数据;CIImage 也包含图片相关数据的图片对象Core Image 层的不可变的;
3. NSData+ImageContentType 图片格式判断
NSData 的分类扩展一个类方法用于判断图片格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   | 
 
 
 
 
  + (NSString *)sd_contentTypeForImageData:(NSData *)data {          uint8_t c;          [data getBytes:&c length:1];     switch (c) {         case 0xFF:             return @"image/jpeg";         case 0x89:             return @"image/png";         case 0x47:             return @"image/gif";         case 0x49:         case 0x4D:             return @"image/tiff";         case 0x52:                          if ([data length] < 12) {                 return nil;             }
              NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];             if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {                 return @"image/webp";             }
              return nil;     }     return nil; }
 
  | 
 
4.UIImage+GIF 生成 GIF  UIImage 对象
UIImage 分类扩展实现对GIF图片的 NSData 的处理,生成 GIF 格式的UIImage 对象,核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
   | 
 
 
 
 
  + (UIImage *)sd_animatedGIFWithData:(NSData *)data {     if (!data) {         return nil;     }
      CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
           size_t count = CGImageSourceGetCount(source);
      UIImage *animatedImage;
           if (count <= 1) {         animatedImage = [[UIImage alloc] initWithData:data];     }     else {         NSMutableArray *images = [NSMutableArray array];
          NSTimeInterval duration = 0.0f;
          for (size_t i = 0; i < count; i++) {             CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);             if (!image) {                 continue;             }
              duration += [self sd_frameDurationAtIndex:i source:source];
              [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
              CGImageRelease(image);         }
          if (!duration) {             duration = (1.0f / 10.0f) * count;         }
          animatedImage = [UIImage animatedImageWithImages:images duration:duration];     }
      CFRelease(source);
      return animatedImage; }
  + (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {     float frameDuration = 0.1f;     CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);     NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;     NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
      NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];     if (delayTimeUnclampedProp) {         frameDuration = [delayTimeUnclampedProp floatValue];     }     else {
          NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];         if (delayTimeProp) {             frameDuration = [delayTimeProp floatValue];         }     }
      if (frameDuration < 0.011f) {         frameDuration = 0.100f;     }
      CFRelease(cfFrameProperties);     return frameDuration; }
 
  | 
 
5.UIImage+MultiFormat
实现 NSData 转 UIImage 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
   | 
 
 
 
 
  + (UIImage *)sd_imageWithData:(NSData *)data {     if (!data) {         return nil;     }          UIImage *image;          NSString *imageContentType = [NSData sd_contentTypeForImageData:data];     if ([imageContentType isEqualToString:@"image/gif"]) {                  image = [UIImage sd_animatedGIFWithData:data];     } #ifdef SD_WEBP     else if ([imageContentType isEqualToString:@"image/webp"])     {         image = [UIImage sd_imageWithWebPData:data];     } #endif     else {         image = [[UIImage alloc] initWithData:data];         UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];                  if (orientation != UIImageOrientationUp) {             image = [UIImage imageWithCGImage:image.CGImage                                         scale:image.scale                                   orientation:orientation];         }     }
      return image; }
 
 
 
 
 
 
  +(UIImageOrientation)sd_imageOrientationFromImageData:(NSData *)imageData {          UIImageOrientation result = UIImageOrientationUp;     CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);     if (imageSource) {                  CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);         if (properties) {             CFTypeRef val;             int exifOrientation;                          val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);             if (val) {                 CFNumberGetValue(val, kCFNumberIntType, &exifOrientation);                 result = [self sd_exifOrientationToiOSOrientation:exifOrientation];             }              CFRelease((CFTypeRef) properties);         } else {                      }         CFRelease(imageSource);     }     return result; }
 
  | 
 
tmp
- webp 图片格式
 
依赖 libwebp 库是 Google 出的图片库,他的好处就是比 jpg 图片格式压缩更厉害小省流量减轻服务器压力等;
- app 执行过程
 
app 执行过程堆栈分析最先调用的是 start 函数(系统内核准备工作动态库加载).dylid 这个库自身也是一个动态库但他的作用是动态链接器 dyld 内部会调用 _dyld_start()函数 再调用 dyldbootstrap::start() 函数,执行了函数之后再走 main 函数,再开启 runloop 走完了之后再开启队列,然后再走 UIApplication 内部方法,再走 didfinsh 方法。
本文标题:SDWebImage 源码阅读解析一
文章作者:zerocc
发布时间:2016年06月03日
原始链接:http://www.zerocc.com.cn/20160603.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!