iOS源码解析—SDWebImage(SDWebImageMan

作者: egoCogito_panf | 来源:发表于2017-04-24 15:59 被阅读65次

    概述

    SDWebImageManager是SDWebImage框架的核心调度类,负责图片的缓存和下载逻辑,本文分析一下该类的相关方法。

    初始化方法

    首先SDWebImageManager维护类几个属性,类的定义注释如下:

    @interface SDWebImageManager ()
    @property (strong, nonatomic, readwrite) SDImageCache *imageCache; //缓存类
    @property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader; //下载类
    @property (strong, nonatomic) NSMutableSet *failedURLs; //下载失败的url
    @property (strong, nonatomic) NSMutableArray *runningOperations; //当前执行的operation
    @end
    

    imageCache用于缓存图片数据的类,具体可以参考第一篇文章,imageDownloader用于下载图片数据,具体可以参考上一篇文章,failedURLs是一个数组,里面存储了获取图片失败对应的url,runningOperations存储了当前正在执行的operation,operation是SDWebImageCombinedOperation类型的对象,定义注释如下:

    @interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
    @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
    @property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock; //cancel block
    @property (strong, nonatomic) NSOperation *cacheOperation; //执行缓存操作的operaton
    @end
    

    SDWebImageOperation是protocol,SDWebImageCombinedOperation实现该protocol,实现了cancel方法。注释如下;

    - (void)cancel {
        self.cancelled = YES;
        if (self.cacheOperation) {
            [self.cacheOperation cancel]; //取消缓存operation的操作
            self.cacheOperation = nil;
        }
        if (self.cancelBlock) {
            self.cancelBlock(); //执行cancelBlock
            _cancelBlock = nil;
        }
    }
    

    然后SDWebImageManager通过-(void)initWithCache:downloader:方法初始化,注释如下:

    - (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader {
        if ((self = [super init])) {
            _imageCache = cache; //赋值cache
            _imageDownloader = downloader; //赋值downloader
            _failedURLs = [NSMutableSet new];
            _runningOperations = [NSMutableArray new];
        }
        return self;
    }
    

    下载图片方法

    SDWebImageManager中的核心方法是-(void)downloadImageWithURL:options:progress:completed:方法,主要负责从缓存中取图片,然后根据策略下载图片。改方法的步骤主要分为以下几步:

    1. 判断前置条件:

      if ([url isKindOfClass:NSString.class]) {
              url = [NSURL URLWithString:(NSString *)url];
          }
          //url判空
          if (![url isKindOfClass:NSURL.class]) {
              url = nil;
          }
       //创建operation对象
          __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
          __weak SDWebImageCombinedOperation *weakOperation = operation;
      
          BOOL isFailedUrl = NO;
          @synchronized (self.failedURLs) {
              isFailedUrl = [self.failedURLs containsObject:url];
          }
       //当前url是否是FailedUrl,如果是直接返回上层。
          if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
              dispatch_main_sync_safe(^{
                  NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
                  completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
              });
              return operation;
          }
      

      首先对url判空,如果不为空,判断当前url是否属于failedURLs中,因为failedURLs中的url属于之前下载失败的图片url,不能被下载。除非options中包含SDWebImageRetryFailed选项,表示之前下载失败过,但是本次需要重新下载。如果不包含SDWebImageRetryFailed选项,调用completedBlock回调。

    2. 将operation加入runningOperations中,从缓存中去图片数据:

      operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
       if (operation.isCancelled) {
               @synchronized (self.runningOperations) {
               [self.runningOperations removeObject:operation];
           }
               return;
          }
           //不存在image,或者需要刷新image,重新下载
       if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
               ...
       }
           else if (image) { //存在image,直接返回
           dispatch_main_sync_safe(^{
               __strong __typeof(weakOperation) strongOperation = weakOperation;
               if (strongOperation && !strongOperation.isCancelled) {
                   completedBlock(image, nil, cacheType, YES, url);
               }
           });
           @synchronized (self.runningOperations) {
               [self.runningOperations removeObject:operation];
           }
           }
       else {
           dispatch_main_sync_safe(^{
               __strong __typeof(weakOperation) strongOperation = weakOperation;
               if (strongOperation && !weakOperation.isCancelled) {
                   completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                   }
               });
               @synchronized (self.runningOperations) {
               [self.runningOperations removeObject:operation];
           }
       }
      }]
      

      该方法首先从缓存中取image,如果没有取到,说明是第一次下载图片,或者存在image,但是需要刷新图片,如果delegate实现shouldDownloadImageForURL方法,返回YES,说明需要下载,这些情况下开始下载图片。当不属于这些情况且image存在时,说明之前已经下载过url对应的图片了,则直接将缓存中的图片通过completedBlock返回,并且将operation从runningOperations中删除。如果image不存在,则返回nil给外部。

    3. 当需要下载图片时,设置标记位,将SDWebImageOptions转化成SDWebImageDownloaderOptions:

      SDWebImageDownloaderOptions downloaderOptions = 0;
      if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
      if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
      if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; //使用NSURLCache
      if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
      if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
      if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
      if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
      if (image && options & SDWebImageRefreshCached) {
       //禁用ProgressiveDownload
       downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
           //开启忽略CachedResponse
       downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
      }
      
    4. 然后调用imageDownloader下载url对应图片数据,并处理不同情况:

      id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
           __strong __typeof(weakOperation) strongOperation = weakOperation;
       if (!strongOperation || strongOperation.isCancelled) {
       }
       else if (error) { //下载失败
           dispatch_main_sync_safe(^{
               if (strongOperation && !strongOperation.isCancelled) {
                   completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                   }
           });
           if (error.code != NSURLErrorNotConnectedToInternet
                  && error.code != NSURLErrorCancelled
                  && error.code != NSURLErrorTimedOut
                  && error.code != NSURLErrorInternationalRoamingOff
                  && error.code != NSURLErrorDataNotAllowed
                  && error.code != NSURLErrorCannotFindHost
                  && error.code != NSURLErrorCannotConnectToHost) {
                   @synchronized (self.failedURLs) {
                       [self.failedURLs addObject:url]; //添加url进failedURLs
                       }
                   }
           }
       else {
           if ((options & SDWebImageRetryFailed)) {
               @synchronized (self.failedURLs) {
                   [self.failedURLs removeObject:url]; //下载成功,将url从failedURLs中删除
               }
           }
               BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
           if (options & SDWebImageRefreshCached && image && !downloadedImage) {
           }
              else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) 
              { //将下载后的图片变换后写入缓存                  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                                  UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
               if (transformedImage && finished) {
                   BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                       [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                  }
                   dispatch_main_sync_safe(^{
                       //回调给外层
                       if (strongOperation && !strongOperation.isCancelled) {
                           completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                      }
                  });
              });
              }
               else { //将下载后的图片数据写入缓存
                   if (downloadedImage && finished) {
                       [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                   }
                   dispatch_main_sync_safe(^{
                       //回调给外层
                       if (strongOperation && !strongOperation.isCancelled) {
                           completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                      }
                  });
              }
          }
           if (finished) {
               @synchronized (self.runningOperations) {
                   if (strongOperation) {
                       //从runningOperations中删除
                       [self.runningOperations removeObject:strongOperation];
                  }
              }
          }
      }];
      operation.cancelBlock = ^{
       [subOperation cancel]; //取消下载
       @synchronized (self.runningOperations) {
               __strong __typeof(weakOperation) strongOperation = weakOperation;
           if (strongOperation) { //从runningOperations中删除
               [self.runningOperations removeObject:strongOperation];
           }
       }
      };
      

      如果下载失败,则返回nil给外层,同时判断errorCode不是超时、取消、找不到主机地址等错误时,将url加入failedURLs。如果下载成功,且option标记为SDWebImageRetryFailed,则键url从failedURLs数组中删除。然后判断是否需要将下载的图片进行变换,如果需要transform,则进行transform后将图片写入缓存,否则直接将原图片写入缓存。当操作执行完后,将operation从runningOperations中删除。最后指定了operation的cancelBlock,该block主要讲负责下载的subOperation取消,然后将当前operation从runningOperations数组中删除。

    其他方法

    SDWebImageManager还提供了一些辅助方法,如下:

    1. -(void)saveImageToCache: forURL:方法,该方法负责将图片存入缓存,url作为key。

      - (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url {
          if (image && url) {
              NSString *key = [self cacheKeyForURL:url];
              [self.imageCache storeImage:image forKey:key toDisk:YES];
          }
      }
      
    2. -(void)cancelAll方法,该方法将runningOperations中的所有operation取消。

      - (void)cancelAll {
          @synchronized (self.runningOperations) {
              NSArray *copiedOperations = [self.runningOperations copy];
              [copiedOperations makeObjectsPerformSelector:@selector(cancel)]; //取消
              [self.runningOperations removeObjectsInArray:copiedOperations];
          }
      }
      

      调用makeObjectsPerformSelector方法遍历operation,调用cancel方法,SDWebImageCombinedOperation的cancel方法在上文有注释,取消cacheOperation,执行cancelBlock取消下载的operation。

    3. -(BOOL)cachedImageExistsForURL:方法和-(BOOL)diskImageExistsForURL:方法

      这两个同步方法返回图片是否在缓存中存在,前一个方法先判断图片是否在内存中存在,如果没有再去文件中查找,返回查找的结果。后一个方法直接去文件中查找,返回查找的结果。

    4. -(void)cachedImageExistsForURL:completion:方法和-(void)diskImageExistsForURL:completion:方法

      这两个方法的功能和上面两个是相同的,前一个方法同时从内存和文件缓存中查找图片是否存在,后一个方法只从文件缓存中查找,区别之处在于,这两个方法是通过completionBlock异步返回结果的。

    相关文章

      网友评论

        本文标题:iOS源码解析—SDWebImage(SDWebImageMan

        本文链接:https://www.haomeiwen.com/subject/dxepzttx.html