SDWebImage 笔记

566 阅读9分钟

此为SDWebImage的源码阅读笔记


  1. 使用GCD时,如果需要比较两个线程是否相等的话,可以使用获取线程的label,然后比较的方法:
// SDWebImageCompat.h
// L104
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
  //...
}

上面的代码会判断当前的线程是否为主线程,因为对于UIImageView的更新的话需要发生在主线程上面。


Downloader

从模块名称我们能看出,这个模块是用于图片下载的。

  1. 下载选项
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
  // 默认模式,
  SDWebImageDownloaderLowPriority = 1 << 0;
  // 渐进式下载,每接收到一段数据就会进行返回,可以一点一点显示图片
  SDWebImageDownloaderProgressiveDownload = 1 << 1;
  // 使用此标志的话,使用NSURLCache
  SDWebImageDownloadUseNSURLCache = 1 << 2;
  // 如果图片是从NSURLCache中读取的话,在completion block中使用的图片参数数据是nil
  SDWebImageDownloaderIgnoreCachedResponse = 1 << 3;
  // 允许在app进入后台后,继续图片的下载
  SDWebImageDownloaderContinueInBackground = 1 << 4;
  // 处理NSHTTPCookieStore中的cookies
  SDWebImageDownloaderHandleCookies = 1 << 5;
  // 允许使用不被信任的证书 SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6;
  // 将图片的下载放到优先级比较高的线程
  SDWebImageDownloaderHighPriority = 1 << 7;
  // 缩小图片
  SDWebImageDownloaderScaleDownLargeImages = 1 << 8;
}
  1. SDWebImage提供了两种图片的下载顺序,分别是SDWebImageDownloadFIFOExecutionOrderSDWebImageDownloaderLIFOExecutionOrder。不难从名称中看出下载队列中图片的执行顺序。

  2. SDWebImageDownloadToken:每个下载都有一个token参数,可以根据token参数的值来取消对应的下载

  3. SDWebImageDownloader是一个同步图片下载和优化器(挑几个自己觉得重要的属性和方法)

  • shouldDecompressImage: 一个布尔值,默认是YES。表明在下载和缓存图片的过程中是否对图片进行解压操作,虽然可以优化图片的显示,但是会话费较多的内存。所以在内存不足的情况下,可以关闭这个属性
  • maxConcurrentDownloads:最大的并行下载数,默认值是6
  • downloadTimeout: 下载的超时时长,默认为15s
  1. SDWebImage内,在下载过程中,下载使用的线程是NSOperationNSOperationQueue组合,而处理下载操作的处理时,还有用到了GCD队列

  2. 下载器的session初始化:

self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                   delegate:self
                                              delegateQueue:nil];

delegate的处理队列传入的参数值是nil,传入nil的话,可以保证session创建一个串行队列来处理方法的调用。会根据图片的下载顺序,对队列中的操作添加依赖

  1. 在根据token的取值取消图片下载的时候,通过dispatch_barrier_async对操作队列进行barrier的操作

  2. 在添加回调block的时候,会在barrierQueue中进行同步操作,设置token相关的参数,并添加到操作字典URLOperations中。(SDWebImageDownloader - `addProgressCallback:completedBlock:forURL:createCallback:)

  3. SDWebImage中,通过继承NSOperation写了一个SDWebImageDownloaderOperation用于下载线程的管理。 在NSOperation中,作者将必要实现的方法抽象出来变成SDWebImageDownloaderOperationInterface,如果需要高度定制下载图片的话,需要实现接口的方法

  4. SDWebImageDownloaderOperation是一个并发操作

  5. (SDWebImageDownloaderOperation.m - L308)使用CGImageSourceCreateWithData将获取到的数据转化为CGImageSourceRef类型(作为data source),在每次获取到新的数据时,都需要拼接到原有的数据上,再传入到方法中,而不是只传入新的数据。

  6. (SDWebImageDownloaderOperation.m - L310 ~ L330)当通过NSURLSession的delegate获取到数据后,如果当前的图片宽和高信息均为0的话,则通过CGImageSourceCopyPropertiesAtIndex()方法获取到CFDictionaryRef的字典,根据kCGImagePropertyPixelHeight, kCGImagePropertyPixelWidthkCGImagePropertyOrientation可以获取到图片的宽,高和方向这三个值(获取图片方向的原因是因为通过Core Graphics绘制时,会丢失方向的参数)

  7. (SDWebImageDownloaderOperation.m - L332 ~ L377)使用CGImageSourceCreateImageAtIndex()来创建CGImage类型的image实例(记住要使用CGImageRelease()进行释放),接下来就是使用Core Graphics框架进行图片的绘制,关键的方法CGBitmapContextCreate()。关于使用Core Graphics进行图片绘制的相关知识的话,可以参考官网文档。这里就不详细介绍了:developer.apple.com/library/con…。当绘制成功之后,转化为UIImage对象,使用imageWithCGImage:scale:orientation来传入方向参数。最后根据需求对图片进行压缩。


Cache

Cache是SDWebImage用于缓存的模块。SDImageCache继承自NSObject,有内存缓存和硬盘缓存两种方式。对于硬盘缓存的话,在进行写操作的时候使用的是异步写入的方式。

  1. 图片的默认缓存时长为1周时间:`static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

  2. 在进行图片缓存的时候,每张图片都有一个唯一的key值,以便对缓存进行查找,key值通常是图片的绝对路径

  3. 在进行缓存时,使用的是一个串行的IO队列来进行写入

  4. 在对SDImageCache进行初始化的时候,注册监听了三个通知:

  • 在接收到内存警告UIApplicationDidReceiveMemoryWarningNotification的时候,会清空内存缓存
  • 在app将要退出UIApplicationWillTerminateNotification的时候,会删除旧文件
  • 在进入后台UIApplicationDidEnterBackgroundNotification时,会在后台线程中删除旧文件
  1. 缓存时,会将图片的路径(即key值)进行MD5取值作为缓存的文件名
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
  const char *str = key.UTF8String;
  if (str == NULL) {
      str = "";
  }
  unsigned char r[CC_MD5_DIGEST_LENGTH];
  CC_MD5(str, (CC_LONG)strlen(str), r);
  NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
                        r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
                        r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];

  return filename;
}
  1. 硬盘缓存的存储路径是在'Library/Caches'路径下。默认情况下,如果没有取消缓存策略的话,都会在内存缓存中(NSCache *memCache)保存一份,对于硬盘存储的则是可选项.

  2. 从缓存获取图片的时候,首先会检查内存缓存,如果内存缓存中没有这张图片的话,就会检查硬盘缓存,如果硬盘缓存中没有,就会开始下载。但是,如果在硬盘缓存中命中的话,在返回图片的同时,会将图片缓存到内存中,提高查找效率。 想要移除图片的话,首先移除内存缓存中的数据,然后再移除硬盘缓存中的。 在SDWebImage中,对硬盘缓存的操作都是在ioQueue的GCD队列中进行异步操作,然后返回到主线程中执行block。避免对硬盘的读写操作会阻塞到主线程界面UI的更新

  3. 删除文件的时候,使用NSDirectoryEnumerator来获取文件的是否为文件夹的标志以及修改日期,大小的信息:

NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

      // This enumerator prefetches useful properties for our cache files.
      NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                 includingPropertiesForKeys:resourceKeys
                                                                    options:NSDirectoryEnumerationSkipsHiddenFiles
                                                               errorHandler:NULL];

然后根据修改日期和需要删除的图片的大小来进行旧文件的删除。


Utils

SDWebImageManager

  1. 在加载图片的过程中,SDWebImage提供几个相关的选项:
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
  /**
   * 默认情况下,当图片URL失效或者下载失败时,该URL会被添加到黑名单中并不会在尝试加载。
   * 此标志可以禁止URL被添加到黑名单中
   */
  SDWebImageRetryFailed = 1 << 0,

  /**
   * 默认情况,图片的加载会发生在UI的交互期间。此标志位的话会将图片的加载延迟到UIScrollView滚动结束的期间
   */
  SDWebImageLowPriority = 1 << 1,

  /**
   * 只将图片缓存到内存中
   */
  SDWebImageCacheMemoryOnly = 1 << 2,

  /**
   * 开启进度下载,当有数据了将加载完成的那部分显示出来,默认情况的话,只会在图片完全加载完毕之后才将图片显示出来
   */
  SDWebImageProgressiveDownload = 1 << 3,

  /**
   * 不管图片是否已经缓存,忽略HTTP响应的缓存控制并且刷新图片
   * 这个标志位的话可以用于同一个URL地址的图片,有的时候,虽然URL相同,但是图片如果有更新的话,缓存机制并不知晓这点。所以需要设置这个标志位来更新图片
   */
  SDWebImageRefreshCached = 1 << 4,

  /**
   * 在iOS 4+, 允许app在进入后台后,继续下载图片。这会在app进入后台时,延长app的驻留时间,在这段时间中来完成图片的加载
   */
  SDWebImageContinueInBackground = 1 << 5,

  /**
   * 通过将NSMutableURLRequest.HTTPShouldHandleCookies设置为YES来处理NSHTTPCookieStore中的cookie
   */
  SDWebImageHandleCookies = 1 << 6,

  /**
   * 允许使用不被信任的证书
   */
  SDWebImageAllowInvalidSSLCertificates = 1 << 7,

  /**
   * 默认情况下,图片会根据队列中的顺序进行加载,这个标志位的话会将图片的优先级提高,也就是将图片的前移
   */
  SDWebImageHighPriority = 1 << 8,
  
  /**
   * 通常情况下,占位图片会在图片加载的过程中显示,这个标志位的话会将占位符延迟到图片加载完成后再进行占位符的加载
   */
  SDWebImageDelayPlaceholder = 1 << 9,

  /**
   * 使用这个标志位来进行图片的变换
   */
  SDWebImageTransformAnimatedImage = 1 << 10,
  
  /**
   * 图片通常会在加载完成后显示到imageView上。但是有时候,可以通过这个标志位来对图片进行修改之后再进行显示
   */
  SDWebImageAvoidAutoSetImage = 1 << 11,
  
  /**
   * 默认情况,图片会根据原始的大小进行解码操作。在iOS中,会根据设备的内存限制进行缩小。
   */
  SDWebImageScaleDownLargeImages = 1 << 12
};
  1. 在下载图片过程中,如果是以下错误的话,则不会将图片URL添加到黑名单当中
  • NSURLErrorNotConnectedToInternet - 无法建立网络连接
  • NSURLErrorCancelled - 任务被取消了
  • NSURLErrorTimedOut - 超时
  • NSURLErrorInternationalRoamingOff - 无法建立漫游网络
  • NSURLErrorDataNotAllowed - 移动网络不允许建立连接
  • NSURLErrorCannotFindHost - 无法解析主机
  • NSURLErrorCannotConnectToHost - 连接主机时失败
  • NSURLErrorNetworkConnectionLost - 连接丢失

基本上从上述的错误原因能看到,当网络出现故障,无法连接到主机的时候,图片的URL不会被添加到黑名单当中,SDWebImage会尝试去重新连接下载

SDWebImageDecoder

  1. 使用CGBitmapContextCreate创建出来的上下文是不含有图片的透明度信息的

  2. decodedImageWithImage:方法实际上返回的就是去除了透明通道信息的图片

  3. 在解码图片时,默认情况下每一个像素还有4个字节


Categories

这个模块是其他Cocoa框架的类的Category

NSData+ImageContentType

通过获取NSData的图片数据的第一个字节来判断图片的格式

switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }

UIImage+GIF

对于GIF图像的话,获取第一帧图片来显示,而完整的GIF图片的播放则由FLAnimatedImageView来完成。

UIImage中有一个images数组属性,对于GIF这种动态图片而言, images为非nil。所以根据这个变量是否为nil可以判断是否为GIF格式

UIImage+MultiFormat

UIImage+WebP

UIView+WebCacheOperation

为UIView添加了一个动态属性字典,用于存储图片的下载操作。


WebCache Categories

这个模块的主要功能是为控件添加图片加载的功能。通过核心方法sd_internalSetImageWithURL:placeholderImage:options:operationKey:setImageBlock:progress:completed:进行图片加载