源碼解析--YYWebImage

封面.png

前言:YYWebImage是YYKit系列中圖片請求的框架嫂伞。作者號稱是可以替代SDWebImage框架孔厉。至于優(yōu)秀的SDWebImage的框架在iOS中的地位想必你一定是知道的拯钻。再開始學習之前先奉上YYWebImage框架的地址帖努。當然你也可以先看看YYWebImage緩存框架的解析

本文準備從YYWebImageDemo中一個網(wǎng)絡請求開始,為了更好的解析這個請求的過程粪般。我們先將Demo中YYWebImageExample類請求url數(shù)組設置為單個

 NSArray *links = @[
         @"https://s-media-cache-ak0.pinimg.com/1200x/2e/0c/c5/2e0cc5d86e7b7cd42af225c29f21c37f.jpg"];

下面是這次請求的方法:

 [_webImageView yy_setImageWithURL:url
                          placeholder:nil
                          options:YYWebImageOptionProgressiveBlur | YYWebImageOptionShowNetworkActivity | YYWebImageOptionSetImageWithFadeAnimation
                          progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                              if (expectedSize > 0 && receivedSize > 0) {
                                  CGFloat progress = (CGFloat)receivedSize / expectedSize;
                                  progress = progress < 0 ? 0 : progress > 1 ? 1 : progress;
                                  if (_self.progressLayer.hidden) _self.progressLayer.hidden = NO;
                                  _self.progressLayer.strokeEnd = progress;
                              }
                          }
                          transform:nil
                          completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
                              if (stage == YYWebImageStageFinished) {
                                  _self.progressLayer.hidden = YES;
                                  [_self.indicator stopAnimating];
                                  _self.indicator.hidden = YES;
                                  if (!image) _self.label.hidden = NO;
                              }
                         }];

我們先針對上面的請求方法看看相關的參數(shù)拼余,如果你使用過SDWebImage,那這些參數(shù)你理解起來應該會比較容易亩歹。你最應該注意的是options枚舉匙监,因為它在整個流程的地方十分重要(作者給的備注英語通俗易懂,就再不做畫蛇添足的翻譯了):

Paste_Image.png

本次網(wǎng)絡請求主要分為下面三個方面:

1.數(shù)據(jù)請求階段前根據(jù)option類型的操作小作。

這個階段主要的操作就是根據(jù)option類型來決定是否直接從內存里面取圖像亭姥。如果不從內存里面拿,是否需要先暫時顯示placeholder顾稀。這都是option來決定的达罗。我們現(xiàn)在要做的是跑通一個流程。Here we go!!!下面的代碼請結合流程一起看静秆。

- (void)yy_setImageWithURL:(NSURL *)imageURL
               placeholder:(UIImage *)placeholder
                   options:(YYWebImageOptions)options
                   manager:(YYWebImageManager *)manager
                  progress:(YYWebImageProgressBlock)progress
                 transform:(YYWebImageTransformBlock)transform
                completion:(YYWebImageCompletionBlock)completion {
    if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
    
    manager = manager ? manager : [YYWebImageManager sharedManager];
    //runtime 取value
    _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
    if (!setter) {
        setter = [_YYWebImageSetter new];
        objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    int32_t sentinel = [setter cancelWithNewURL:imageURL];
    //回到主線程
    _yy_dispatch_sync_on_main_queue(^{
        //如果option有YYWebImageOptionSetImageWithFadeAnimation且沒有YYWebImageOptionAvoidSetImage類型
        if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
            !(options & YYWebImageOptionAvoidSetImage)) {
            if (!self.highlighted) {
                //移除動畫
                [self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
            }
        }
        if (!imageURL) {//當imageURL為空
            //options沒有YYWebImageOptionIgnorePlaceHolder類型則直接把placeholder賦給image
            if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
                self.image = placeholder;
            }
            return;
        }
        
        // get the image from memory as quickly as possible
        UIImage *imageFromMemory = nil;
        //如果options 沒有YYWebImageOptionUseNSURLCache粮揉,YYWebImageOptionRefreshImageCache類型
        if (manager.cache &&
            !(options & YYWebImageOptionUseNSURLCache) &&
            !(options & YYWebImageOptionRefreshImageCache)) {
            //根據(jù)key去YYCache里面取UIImage
            imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
        }
        
        if (imageFromMemory) {
            //如果options 沒有YYWebImageOptionAvoidSetImage類型
            if (!(options & YYWebImageOptionAvoidSetImage)) {
                self.image = imageFromMemory;
            }
            if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
            return;
        }
       //如果options 沒有YYWebImageOptionUseNSURLCache
        if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
            self.image = placeholder;
        }
        
//============上面是文中數(shù)據(jù)請求階段前根據(jù)option類型的操作====================================
        
//因為為了避免下載大尺寸文件或者圖片內存爆掉,所以NSURLConnection會把文件或圖片分成多段回調抚笔。下面就是拿到總expectedSize和每次接收到的receivedSize的block聲明
        __weak typeof(self) _self = self;
        dispatch_async([_YYWebImageSetter setterQueue], ^{
            YYWebImageProgressBlock _progress = nil;
            if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    progress(receivedSize, expectedSize);
                });
            };

            __block int32_t newSentinel = 0;
            __block __weak typeof(setter) weakSetter = nil;
//====================================_completion==================================
// 完成的回調聲明
            YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
                __strong typeof(_self) self = _self;
                BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
                

                dispatch_async(dispatch_get_main_queue(), ^{
                    BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
                    if (setImage && self && !sentinelChanged) {
                        BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted);
                        if (showFade) {
                            CATransition *transition = [CATransition animation];
                            transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
                            transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
                            transition.type = kCATransitionFade;
                            [self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
                        }
                        self.image = image;
                    }
                    if (completion) {
                        if (sentinelChanged) {
                            completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
                        } else {
                            completion(image, url, from, stage, error);
                        }
                    }
                });
            };
//====================================_completion==================================

            //將相關參數(shù)和聲明的block傳過去
            newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
            weakSetter = setter;
        });
    });
}
1.png

2.加入隊列數(shù)據(jù)請求階段

這部分代碼跳轉太多扶认,便不貼相關代碼了。你運行代碼應該能獲取下圖相關的流程殊橙。


2.png

3.NSURLConnection回調的處理

// 在構建connection會被響應辐宾。如果這個connection需要根據(jù)NSURLCredentialStorage中的權限進行構建,那么就返回YES
// 默認是YES膨蛮,想要修改螃概,需要用戶自己指定self.shouldUseCredentialStorage值
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection 
//客戶端開始認證挑戰(zhàn)(Authentication Challenge)
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
//返回相應頭的信息。重要的有statusCode鸽疾,expectedContentLength吊洼。回調給size制肮,數(shù)據(jù)接收的開始階段冒窍。
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
//文件或者圖片分割成多份回調
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
//文件下載完畢后的回調
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
//下載失敗递沪,失敗回調
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 

流程跑完了,我們現(xiàn)在來看下類圖:

類圖.png

YYWebImage使用YYImageCache作緩存(YYCache上層)综液,YYImage做解碼相關工作款慨。利用Category分類做提供接口。而_YYWebImageSetter是一個私有類谬莹,為categories提供接口檩奠,值得一提的是它使用OSAtomicIncrement32來原子操作級別地遞增_sentinel。下面我們主要來看看YYWebImageManager 附帽,YYWebImageOperation埠戳。

YYWebImageManager

YYWebImageManager是YYWebImage的管理類,主要提供了下面管理:
1.圖片操作的枚舉YYWebImageOptions
2.圖片的來源的枚舉YYWebImageFromType
3.請取圖片狀態(tài)的枚舉YYWebImageStage
4.進度的回調block YYWebImageProgressBlock
5.對圖片附加的操作block YYWebImageTransformBlock
6.請求圖片完成的block YYWebImageCompletionBlock

YYWebImageOperation

YYWebImageOperationNSOperation的子類蕉扮,是對一個請求任務的包裝來從URL獲取圖片整胃。它的屬性executingfinished喳钟,cancelled屁使,started控制著任務的狀態(tài)。通過把任務添加到隊列奔则,或者手動調用start方法執(zhí)行蛮寂。當一個任務開始:
1.從磁盤里面請求圖片,如果存在易茬。返回并completion block回調酬蹋。
2.開始URL connection請求圖片,如果設置了YYWebImageProgressBlock疾呻,則返回每一段的Progress
3.如果設置了YYWebImageTransformBlock除嘹,則依據(jù)block 處理圖片
4.把圖片存入磁盤。返回并completion block回調岸蜗。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末尉咕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子璃岳,更是在濱河造成了極大的恐慌年缎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铃慷,死亡現(xiàn)場離奇詭異单芜,居然都是意外死亡,警方通過查閱死者的電腦和手機犁柜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門洲鸠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事扒腕【畹恚” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵瘾腰,是天一觀的道長皆的。 經(jīng)常有香客問我,道長蹋盆,這世上最難降的妖魔是什么费薄? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮栖雾,結果婚禮上楞抡,老公的妹妹穿的比我還像新娘。我一直安慰自己岩灭,他們只是感情好拌倍,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布赂鲤。 她就那樣靜靜地躺著噪径,像睡著了一般。 火紅的嫁衣襯著肌膚如雪数初。 梳的紋絲不亂的頭發(fā)上找爱,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音泡孩,去河邊找鬼车摄。 笑死,一個胖子當著我的面吹牛仑鸥,可吹牛的內容都是我干的吮播。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼眼俊,長吁一口氣:“原來是場噩夢啊……” “哼意狠!你這毒婦竟也來了?” 一聲冷哼從身側響起疮胖,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤环戈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后澎灸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體院塞,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年性昭,在試婚紗的時候發(fā)現(xiàn)自己被綠了拦止。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡糜颠,死狀恐怖汹族,靈堂內的尸體忽然破棺而出艺玲,到底是詐尸還是另有隱情,我是刑警寧澤鞠抑,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布饭聚,位于F島的核電站,受9級特大地震影響搁拙,放射性物質發(fā)生泄漏秒梳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一箕速、第九天 我趴在偏房一處隱蔽的房頂上張望酪碘。 院中可真熱鬧,春花似錦盐茎、人聲如沸兴垦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽探越。三九已至,卻和暖如春窑业,著一層夾襖步出監(jiān)牢的瞬間钦幔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工常柄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鲤氢,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親估盘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫相种、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,029評論 4 62
  • 前不久做了一個生成快照的需求东抹,其中用到 SDWebImage 來下載圖片蚂子,在使用該框架的過程中也遇到了一些問題,索...
    ShannonChenCHN閱讀 14,053評論 12 241
  • 春晚缭黔,大家都看了嗎食茎?我看了,確切地說馏谨,大概的看了别渔!現(xiàn)在想想,最后一次認真看春晚是何年?想不起來哎媚,也不想去想了喇伯!反正...
    Rock公主閱讀 103評論 0 0
  • 為什么你總是對孩子發(fā)火买喧?表面上看捻悯,是你處理情緒的能力差,因為被惡劣的情緒左右著淤毛,變得激憤偏執(zhí)今缚,暴跳如雷。而最根本的...
    無事閑逛的伊麗閱讀 374評論 0 7
  • 清晨 濕潤的空氣飄散著泥土的氣息 天真藍 藍透了我的雙眼 太陽金黃 黃遍了我的周身 我來了 走向這剛剛睡醒的土地 ...
    魏加恩閱讀 244評論 0 0