通俗易懂的SDWebImage源碼解析

  • Asynchronous image downloader with cache support as a UIImageView category
    *一個(gè)異步的圖片下載與緩存的UIImageViewCategory

對于它究竟是如何工作的的榛,相信大家應(yīng)該或多或少都已經(jīng)有所了解丽涩。但是它內(nèi)部是怎么實(shí)現(xiàn),又有那些細(xì)節(jié)旋圆,我卻一直犯懶沒有真正的好好去研究過,最近不是很忙幌羞,于是我就仔細(xì)的研究了一下它的實(shí)現(xiàn)細(xì)節(jié)求冷,這里我源碼的版本為4.0.0,也就是當(dāng)前最新的版本辱挥。

**在這里我推薦大家去github下載對應(yīng)的源碼,一邊看blog一邊看對應(yīng)的源碼边涕,最好再做上自己的注釋晤碘,這樣會看的更快,且做做筆記會加深自己的映像功蜓,SDWebImage:github地址:https://github.com/rs/SDWebImage **

好了园爷,廢話不多說,直接來看代碼吧式撼。
下面的代碼是我們經(jīng)常使用的SDWebImage的方法之一腮介,給imageView傳入對應(yīng)的圖片url和占位圖片,它就幫我們實(shí)現(xiàn)了圖片的所有操作端衰。

點(diǎn)進(jìn)它的具體實(shí)現(xiàn)叠洗,可以看到它是一個(gè)UIImageView的分類,分類的調(diào)用方法如下旅东,我已經(jīng)給對應(yīng)的參數(shù)做出了對應(yīng)的翻譯:

/**
 * 使用一個(gè)url灭抑,占位圖片和自定義選項(xiàng)來設(shè)置imageView
 * 下載是異步且緩存的
 * url                          圖像的url
 * placeholder                  占位圖片,初始化時(shí)被設(shè)置抵代,在請求結(jié)束時(shí)消失
 * options                      在下載圖片的時(shí)候使用的選項(xiàng)腾节,看SDWebImageOptions有哪些可能的值
 * progressBlock                當(dāng)圖像下載時(shí)候調(diào)用的block,這個(gè)block在一個(gè)后臺隊(duì)列執(zhí)行
 * completedBlock               當(dāng)操作結(jié)束時(shí)調(diào)用的block,這個(gè)block沒有返回值,把請求到的圖像作為第一個(gè)參數(shù)荤牍,如果發(fā)生錯(cuò)誤的話案腺,第一個(gè)參數(shù)為空,第二個(gè)參數(shù)會包含一個(gè)NSError對象康吵,第三個(gè)參數(shù)是一個(gè)bool值劈榨,指是從本地緩存還是從網(wǎng)絡(luò)來重新獲取圖像,第四個(gè)參數(shù)是圖片原始的url
 */
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                        operationKey:nil
                       setImageBlock:nil
                            progress:progressBlock
                           completed:completedBlock];
}

相信大家對上面的參數(shù)晦嵌,并不陌生同辣,即使曾經(jīng)沒研究過,看到對應(yīng)的名稱和注釋也能大概猜出它們的作用惭载,這里唯一不太了解的應(yīng)該是options的含義了旱函。

options是一個(gè)枚舉,下面是options對應(yīng)的值描滔,作用已經(jīng)添加在注釋中了

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    /**
     * 默認(rèn)情況下,如果一個(gè)url在下載的時(shí)候失敗了,那么這個(gè)url會被加入黑名單并且library不會嘗試再次下載,這個(gè)flag會阻止library把失敗的url加入黑名單(簡單來說如果選擇了這個(gè)flag,那么即使某個(gè)url下載失敗了,sdwebimage還是會嘗試再次下載他.)
     */
    SDWebImageRetryFailed = 1 << 0,

    /**
     * UI交互期間下載
     * 導(dǎo)致延遲下載在UIScrollView減速的時(shí)候棒妨,(也就是你滑動(dòng)的時(shí)候scrollview不下載,你手從屏幕上移走,scrollview開始減速的時(shí)候才會開始下載圖片)
     */
    SDWebImageLowPriority = 1 << 1,

    /**
     * 只進(jìn)行內(nèi)存緩存,不進(jìn)行磁盤緩存
     */
    SDWebImageCacheMemoryOnly = 1 << 2,

    /**
     * 這個(gè)標(biāo)志可以漸進(jìn)式下載,顯示的圖像是逐步在下載(就像你用瀏覽器瀏覽網(wǎng)頁的時(shí)候那種圖片下載,一截一截的顯示
     */
    SDWebImageProgressiveDownload = 1 << 3,

    /**
     * 即使圖像緩存含长,也要遵守HTTP響應(yīng)緩存控制券腔,如果需要伏穆,可以從遠(yuǎn)程位置刷新圖像
     * 磁盤緩存將由NSURLCache而不是SDWebImage處理,導(dǎo)致輕微的性能降低颅眶。
     * 這個(gè)選項(xiàng)幫助處理在同樣的網(wǎng)絡(luò)請求地址下圖片的改變
     * 如果刷新緩存的圖像蜈出,完成的block會在使用緩存圖像的時(shí)候調(diào)用,還會在最后的圖像被調(diào)用
     * 當(dāng)你不能使你的URL靜態(tài)與嵌入式緩存
     */
    SDWebImageRefreshCached = 1 << 4,

    /**
     * 在iOS4以上涛酗,如果app進(jìn)入后臺铡原,也保持下載圖像,這個(gè)需要取得用戶權(quán)限
     * 如果后臺任務(wù)過期商叹,操作將被取消
     */
    SDWebImageContinueInBackground = 1 << 5,

    /**
     * 操作cookies存儲在NSHTTPCookieStore通過設(shè)置NSMutableURLRequest.HTTPShouldHandleCookies = YES
     */
    SDWebImageHandleCookies = 1 << 6,

    /**
     * 允許使用無效的SSL證書
     * 用戶測試剖笙,生成情況下小心使用
     */
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

    /**
     * 優(yōu)先下載
     */
    SDWebImageHighPriority = 1 << 8,
    
    /**
     * 在加載圖片時(shí)加載占位圖。 此標(biāo)志將延遲加載占位符圖像弥咪,直到圖像完成加載过蹂。
     */
    SDWebImageDelayPlaceholder = 1 << 9,

    /**
     * 我們通常不調(diào)用transformDownloadedImage代理方法在動(dòng)畫圖像上聚至,大多數(shù)情況下會對圖像進(jìn)行耗損
     * 無論什么情況下都使用
     */
    SDWebImageTransformAnimatedImage = 1 << 10,
    
    /**
     * 圖片在下載后被加載到imageView。但是在一些情況下扳躬,我們想要設(shè)置一下圖片(引用一個(gè)濾鏡或者加入透入動(dòng)畫)
     * 使用這個(gè)來手動(dòng)的設(shè)置圖片在下載圖片成功后
     */
    SDWebImageAvoidAutoSetImage = 1 << 11,
    
    /**
     * 圖像將根據(jù)其原始大小進(jìn)行解碼脆诉。 在iOS上贷币,此標(biāo)記會將圖片縮小到與設(shè)備的受限內(nèi)存兼容的大小役纹。
     */
    SDWebImageScaleDownLargeImages = 1 << 12
};

看完上面的枚舉值字管,大家應(yīng)該還是不知道有什么作用,沒關(guān)系,接著往下看硫戈。

繼續(xù)往后可以看到下硕,最終它真正調(diào)用的是UIView+WebCache.h的方法,這里就是要詳細(xì)講解的第一個(gè)方法嫩码,在下面的代碼中我已經(jīng)貼了一些注釋來方便講解:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock {
    // 獲取可用的operationKey
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    // 取消該key對應(yīng)的任務(wù)
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    // 給該視圖的實(shí)例對象設(shè)置一個(gè)屬性
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    // 如果options不為SDWebImageDelayPlaceholder铸题,那么先把placeholder設(shè)置到該視圖上
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    
    if (url) {
        // check if activityView is enabled or not
        // 如果有url丢间,且設(shè)置顯示ActivityIndicator烘挫,那么顯示
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
        
        __weak __typeof(self)wself = self;
        // ??這里的operation不是繼承自NSOperation的柬甥,我們可以把它看做一個(gè)關(guān)聯(lián)視圖操作的對象苛蒲,我們稱它為op對象
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            // 圖像下載成功后撤防,移除ActivityIndicator
            [sself sd_removeActivityIndicator];
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                // 如果有image且options為SDWebImageAvoidAutoSetImage且有completedBlock
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    // 在這里獲取到圖片寄月,且做一些加工的操作
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    // 如果有image漾肮,設(shè)置視圖的圖像
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    // 標(biāo)記設(shè)為需要布局
                    [sself sd_setNeedsLayout];
                } else {
                    // image已經(jīng)嘗試獲取過了克懊,但是沒有從網(wǎng)絡(luò)端獲取到
                    // 如果options為SDWebImageDelayPlaceholder,當(dāng)前視圖設(shè)置為占位圖片
                    // 標(biāo)記設(shè)為需要布局
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                // 有completedBlock且下載finished為yes墙懂,將需要的參數(shù)傳出去
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        // 將現(xiàn)在的op對象加到對應(yīng)的視圖實(shí)例中
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
        // 如果url為空损搬,拋出錯(cuò)誤
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

下面我們將一步步來解釋這些代碼的含義:
1.首先先獲取validOperationKey巧勤,如果為空颅悉,那么就獲取到當(dāng)前類的名稱剩瓶,查看UIImageView+WebCache.h對應(yīng)的傳入?yún)?shù),可以發(fā)現(xiàn)UIImageView傳入的對應(yīng)validOperationKeynil吠架,也就是說默認(rèn)情況下傍药,如果我們不直接給validOperationKey賦值魂仍,它就為nil擦酌,那么這里獲得的validOperationKey一般也就是對應(yīng)類的class赊舶,也就是說如果是UIImageView調(diào)用這個(gè)方法笼平,那么對應(yīng)的validOperationKey也就是UIImageView

NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);

2.取消該key對應(yīng)的任務(wù)

[self sd_cancelImageLoadOperationWithKey:validOperationKey];

什么情況锌唾,怎么還沒開始做事情就開始取消了晌涕?

在這里我們做一個(gè)標(biāo)記余黎,一會來解釋
??標(biāo)記1:- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key是什么意思驯耻,為什么一來就取消炒考,有什么作用斋枢?

3.給該視圖的實(shí)例對象設(shè)置一個(gè)屬性瓤帚,這里的知識是使用了runtime戈次,如果對runtime不夠了解的,可以參看資料:讓你快速上手Runtime绊寻。

通俗點(diǎn)講:這里的作用就是給UIView的實(shí)例添加了@property (nonatomic, strong) NSString *url;澄步,只是這個(gè)屬性的獲取方式是通過key/value的方式來獲得的村缸,url這個(gè)value對應(yīng)的key&imageURLKey

objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

4.接下來就是設(shè)置placeholder梯皿,如果不想讓SDWebImage來幫你設(shè)置占位圖片东羹,就給它傳入setImageBlock來自定義設(shè)置占位圖片百姓。

// 如果options不為SDWebImageDelayPlaceholder况木,那么先把placeholder設(shè)置到該視圖上
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }

這里有兩個(gè)需要講解的

  • options & SDWebImageDelayPlaceholder: &是按位與
    舉個(gè)例子:a & b a=1 b=2 a== 0000 0001(二進(jìn)制) b== 0000 0010(二進(jìn)制) a & b = 0000 0000(二進(jìn)制)
    放在這里就是求类,如果options中包含SDWebImageDelayPlaceholder屹耐,那么就不設(shè)置占位圖。
  • dispatch_main_async_safe:這是一個(gè)定義的宏
    如果當(dāng)前是主進(jìn)程犯眠,就直接執(zhí)行block筐咧,否則把block放到主進(jìn)程運(yùn)行量蕊。為什么要判斷是否是主進(jìn)程残炮?因?yàn)閕OS上任何UI的操作都在主線程上執(zhí)行缩滨,所以主進(jìn)程還有一個(gè)名字楷怒,叫做“UI進(jìn)程”鸠删。

ifndef dispatch_main_async_safe

define dispatch_main_async_safe(block)\

if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
    block();\
} else {\
    dispatch_async(dispatch_get_main_queue(), block);\
}

endif


5.下面的操作是根據(jù)url來加載網(wǎng)絡(luò)圖片刃泡,分為有`url`有值和`url`無值的情況
```obj
  if (url) {
      // check if activityView is enabled or not
      if ([self sd_showActivityIndicatorView]) {
          [self sd_addActivityIndicator];
      }
      
      __weak __typeof(self)wself = self;
      id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
          __strong __typeof (wself) sself = wself;
          [sself sd_removeActivityIndicator];
          if (!sself) {
              return;
          }
          dispatch_main_async_safe(^{
              if (!sself) {
                  return;
              }
              if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                  completedBlock(image, error, cacheType, url);
                  return;
              } else if (image) {
                  [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                  [sself sd_setNeedsLayout];
              } else {
                  if ((options & SDWebImageDelayPlaceholder)) {
                      [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                      [sself sd_setNeedsLayout];
                  }
              }
              if (completedBlock && finished) {
                  completedBlock(image, error, cacheType, url);
              }
          });
      }];
      [self sd_setImageLoadOperation:operation forKey:validOperationKey];
  } else {
      dispatch_main_async_safe(^{
          [self sd_removeActivityIndicator];
          if (completedBlock) {
              NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
              completedBlock(nil, error, SDImageCacheTypeNone, url);
          }
      });
  }
  • 先來分析url無值的情況禁添,也就是上面代碼中的else老翘,可以很清晰的看到先會調(diào)用[self sd_removeActivityIndicator];铺峭,根據(jù)名字我們大概能猜到是移除一個(gè)ActivityIndicator卫键,然后會使用完成的block在主線程拋出一個(gè)NSError對象虱朵。

  • 現(xiàn)在來看url有值的情況,首先

          // 如果有url梆暮,且設(shè)置顯示ActivityIndicator惕蹄,那么顯示
          if ([self sd_showActivityIndicatorView]) {
              [self sd_addActivityIndicator];
          }
    

    然后通過SDWebImageManager的單例對象調(diào)用下面的方法,返回了一個(gè)名為operationid類型的對象

    - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                       options:(SDWebImageOptions)options
                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                     completed:(nullable SDInternalCompletionBlock)completedBlock;
    

    先不看這個(gè)方法的實(shí)現(xiàn)张峰,先猜一猜這個(gè)方法是做什么的喘批?

我想你肯定已經(jīng)猜到了饶深,這個(gè)方法就是下載圖片且給UIImageView設(shè)置圖片的方法

現(xiàn)在先來看看這個(gè)方法完成的block中的代碼:

            __strong __typeof (wself) sself = wself;
            // 圖像下載成功后敌厘,移除ActivityIndicator
            [sself sd_removeActivityIndicator];
            // 如果self為nil朽合,直接返回
            if (!sself) {
                return;
            }

然后如果獲取到圖片曹步,options中包含SDWebImageAvoidAutoSetImage讲婚,且完成的block不為空的情況下筹麸,直接調(diào)用完成block返回

// 如果有image且options為SDWebImageAvoidAutoSetImage且有completedBlock
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
  // 在這里獲取到圖片竹捉,且做一些加工的操作
  completedBlock(image, error, cacheType, url);
  return;
}

如果沒有獲取到optionsSDWebImageAvoidAutoSetImage块差,但是獲取到了image,直接設(shè)置對應(yīng)視圖的image

else if (image) {
    // 如果有image需五,設(shè)置視圖的圖像
    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
    // 標(biāo)記設(shè)為需要布局
    [sself sd_setNeedsLayout];
}

然后就是當(dāng)image沒有獲取到的時(shí)候的操作宏邮,如果之前設(shè)置的optionsSDWebImageDelayPlaceholder(也就是延遲加載占位圖)蜜氨,那么現(xiàn)在也應(yīng)該把占位圖設(shè)置上了

else {
    // image已經(jīng)嘗試獲取過了飒炎,但是沒有從網(wǎng)絡(luò)端獲取到
    // 如果options為SDWebImageDelayPlaceholder笆豁,當(dāng)前視圖設(shè)置為占位圖片
    if ((options & SDWebImageDelayPlaceholder)) {
        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        [sself sd_setNeedsLayout];
    }

最后闯狱,在所有的判斷結(jié)束以后哄孤,通過completedBlock將對應(yīng)的參數(shù)傳遞出去

// 有completedBlock且下載finished為yes录豺,將需要的參數(shù)傳出去
if (completedBlock && finished) {
  completedBlock(image, error, cacheType, url);
}

在url不為nil的邏輯代碼的最后双饥,將前面生成的operation和最開始獲取到的validOperationKey設(shè)置到對應(yīng)的視圖咏花,也就是下面的代碼;韬病E锞铡统求!

在這里我們再做一個(gè)標(biāo)記,下面來解釋
??標(biāo)記2:- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key又是什么意思另假,和上面的標(biāo)記1有什么關(guān)系怕犁?

// 將現(xiàn)在的op對象加到對應(yīng)的視圖實(shí)例中
[self sd_setImageLoadOperation:operation forKey:validOperationKey];

上面對對應(yīng)的邏輯進(jìn)行大概的梳理奏甫,大家應(yīng)該學(xué)習(xí)到了一些扶檐,但是有些地方肯定還是不清楚款筑,所以看下面吧

下面是解決問題的時(shí)間

第一個(gè)問題

  • 首先看到??標(biāo)記1和上面的??標(biāo)記2
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];

在所有操作剛開始執(zhí)行的時(shí)候,視圖就執(zhí)行了個(gè)取消的操作解虱,最后又給視圖增加了一個(gè)operation殴泰,這到底是怎么回事悍汛?

1.根據(jù)經(jīng)驗(yàn)离咐,如果要給一個(gè)UIImageView設(shè)置image宵蛀,那么肯定要獲取到對應(yīng)的image术陶,如果這是一個(gè)網(wǎng)絡(luò)圖片梧宫,那么肯定是要將這個(gè)圖片下載,然后下載好了兆解,再將圖片設(shè)置到對應(yīng)的UIImageView锅睛,相信大家對這個(gè)邏輯是沒有異議的现拒。

2.現(xiàn)在下載圖片對應(yīng)的操作就是id <SDWebImageOperation> operation來執(zhí)行印蔬,一開始的取消操作就是取消了這樣一個(gè)任務(wù)
注意:這里的operation可不是繼承自NSOperation的對象侥猬,而是一個(gè)繼承自NSObject的對象退唠,你可以將它看做一個(gè)操作圖片更新的對象

3.看一下- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key對應(yīng)的實(shí)現(xiàn)瞧预,首先通過[self operationDictionary]獲取到存有operation的字典(這里的字典也是通過runtime動(dòng)態(tài)來添加的)垢油,然后通過對應(yīng)的key取出對應(yīng)的operation滩愁,調(diào)用cancel來取消對應(yīng)的操作惊楼,然后通過key移除對應(yīng)的operation檀咙。

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    // operation的字典
    SDOperationsDictionary *operationDictionary = [self operationDictionary];
    id operations = operationDictionary[key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

4.接著看一下對應(yīng)的設(shè)置方法,設(shè)置方法中先調(diào)用了sd_cancelImageLoadOperationWithKey棕诵,然后再將對應(yīng)的operation添加到了字典中

- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
    if (key) {
        [self sd_cancelImageLoadOperationWithKey:key];
        if (operation) {
            SDOperationsDictionary *operationDictionary = [self operationDictionary];
            operationDictionary[key] = operation;
        }
    }
}

下面我舉個(gè)例子來講一下這么做的作用:

在常用的tableViewcell上有圖片是再常見不過的了校套,如下所示的這種cell

cell.png

  • 在我們使用SDWebImage給上面的cell中的imageview設(shè)置網(wǎng)絡(luò)圖片的時(shí)候侨把,圖片的下載是異步的秋柄,那么如果現(xiàn)在給當(dāng)前cell設(shè)置的為cell.imageviewa.png蠢正,隨著tableView的滑動(dòng)骇笔,這個(gè)cell會被復(fù)用,復(fù)用后現(xiàn)在cell.imageviewb.png嚣崭,這里的a.pngb.png都是從網(wǎng)絡(luò)上異步下載的,不是本地的資源圖片
  • 一開始cellindex為1雹舀,imagea旭旭,復(fù)用以后cellindex為6,imageb葱跋,按道理來說圖片應(yīng)該先為a源梭,然后為b娱俺,但是a很大,b很小废麻,b都已經(jīng)下載好了荠卷,a還沒有下載好,當(dāng)滑動(dòng)到顯示index為6的cell的時(shí)候烛愧,cell的圖片先顯示的b油宜,因?yàn)?code>b已經(jīng)下載好了,過了一會怜姿,a也下載好了
    那么神奇的事情發(fā)生了慎冤,index為6的cell中的圖片ab覆蓋了,應(yīng)該顯示b的變成顯示a
  • 整個(gè)數(shù)據(jù)都亂了沧卢,這實(shí)在太可怕了

如果上面我舉的例子沒看懂蚁堤,請反復(fù)多看幾遍!但狭!

好披诗,我現(xiàn)在認(rèn)為你已經(jīng)看懂了~

[self sd_cancelImageLoadOperationWithKey:validOperationKey];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];

上面??的兩個(gè)方法就是為了防止這種情況的發(fā)生撬即,因此先取消對應(yīng)的圖片操作,再重新添加呈队,剛開始先通過key獲取operation剥槐,如果有operation對象---->取消。當(dāng)重新產(chǎn)生一個(gè)operation對象以后宪摧,還是看對應(yīng)的字典中有沒有粒竖,有----> 取消(因?yàn)楝F(xiàn)在還沒將新產(chǎn)生的operation添加到字典中),沒有--->operationDictionary[key] = operation;绍刮,將這個(gè)operation放到字典中温圆,這樣就可以保證一個(gè)視圖對象只有一個(gè)operation在操作圖像
在這里也就是說如果設(shè)置了cell的網(wǎng)絡(luò)圖片為b,那么就取消掉之前的a的相關(guān)操作,這樣就不會出現(xiàn)顯示錯(cuò)亂的問題了孩革。
作者的想法真的是很聰明呀!

第二個(gè)問題

在SDWebImage中常乘昵福可以看到options & SDWebImageRefreshCached這種寫法,查看SDWebImageRefreshCached的定義可以看到SDWebImageRefreshCached = 1 << 4膝蜈。
例如:a=1 b=2 a== 0000 0001(二進(jìn)制) b== 0000 0010(二進(jìn)制) a & b = 0000 0000 (二進(jìn)制) 十進(jìn)制為0
也就是說SDWebImageRefreshCached是將1左移4位的一個(gè)值锅移,二進(jìn)制表示為00010000,十進(jìn)制為16

在接下來的代碼中還會看到downloaderOptions | SDWebImageDownloaderLowPriority饱搏,這種寫法是按位或非剃,也是位運(yùn)算的一種:
例如:a=5,b=11; 5 ==0000 0101 (二進(jìn)制) 10==0000 1011(二進(jìn)制) a | b== 0000 1111(二進(jìn)制) 十進(jìn)制為15

如果想了解更多的相關(guān)知識,可以參考這篇博客:按位與推沸,按位或

總結(jié)

我用了一張流程圖來表示這篇文章的內(nèi)容备绽,方便大家查看

流程圖.png

以上是一些我的個(gè)人理解,如果有什么不對的地方也希望大家能夠指出鬓催,互相學(xué)習(xí)肺素!
這是SDWebImage源碼解析的第一篇,下一篇將會對下面產(chǎn)生operation的方法進(jìn)行分析宇驾,歡迎大家關(guān)注倍靡!

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock
laosiji.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市课舍,隨后出現(xiàn)的幾起案子塌西,更是在濱河造成了極大的恐慌,老刑警劉巖筝尾,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捡需,死亡現(xiàn)場離奇詭異,居然都是意外死亡忿等,警方通過查閱死者的電腦和手機(jī)栖忠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庵寞,你說我怎么就攤上這事狸相。” “怎么了捐川?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵脓鹃,是天一觀的道長。 經(jīng)常有香客問我古沥,道長瘸右,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任岩齿,我火速辦了婚禮太颤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盹沈。我一直安慰自己龄章,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布乞封。 她就那樣靜靜地躺著做裙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肃晚。 梳的紋絲不亂的頭發(fā)上锚贱,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天,我揣著相機(jī)與錄音关串,去河邊找鬼拧廊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛晋修,可吹牛的內(nèi)容都是我干的卦绣。 我是一名探鬼主播,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼飞蚓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了廊蜒?” 一聲冷哼從身側(cè)響起趴拧,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎山叮,沒想到半個(gè)月后著榴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屁倔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年脑又,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,928評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡问麸,死狀恐怖往衷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情严卖,我是刑警寧澤席舍,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站哮笆,受9級特大地震影響来颤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稠肘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一福铅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧项阴,春花似錦钮惠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薯演,卻和暖如春撞芍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跨扮。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工序无, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衡创。 一個(gè)月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓帝嗡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親璃氢。 傳聞我的和親對象是個(gè)殘疾皇子哟玷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容