SDWebImage源碼分析 1

前言

開發(fā)iOS有一段時間了蜗细,平時工作中主要還是完成業(yè)務功能蜈项。類似網(wǎng)絡請求烙无,圖片加載等等都直接使用現(xiàn)成的開源類庫名党,項目主要還是以穩(wěn)定為先。
但長期這樣感覺難以進步质蕉,想要進階除了看書外就得多看看開源類庫的源碼了势篡。
于是就從SDWebImage入手,在深入學習后發(fā)現(xiàn)它的代碼各層職責分工明確模暗,代碼量也不是很多禁悠,利用業(yè)余時間斷斷續(xù)續(xù)學習花費了大約三周時間,感覺比較適合作為第一個供學習的開源類庫兑宇。

大致涉及到的知識點:

  • Block
  • GCD
  • NSOperation
  • Associated Objects
  • NSURLRequest
  • NSCache
  • 圖片類型識別與處理

文章中難免出現(xiàn)問題碍侦,望各位給予糾正,有問題歡迎一起討論隶糕。

源碼分析

SDWebImage使用起來非常簡單瓷产,只需調(diào)用sd_setImageWithURL方法,就可以將圖片異步的加載并顯示在UIImageView上。

所以接下來我們就從sd_setImageWithURL開始說起:

NSURL * url = [NSURL URLWithString:@"http://hbimg.b0.upaiyun.com/ddd2cee8ff21d4a09a86b68972b78b15ba7bc2a035fa4-sGYzEJ_fw658"];
[imageView sd_setImageWithURL:url];

上面代碼所使用的是sd_setImageWithURL最簡單的版本枚驻,我們跟進去看一下濒旦,發(fā)現(xiàn)方法里其實幫我們設置好了默認參數(shù),最終調(diào)用到的是另一個方法:

- (void)sd_setImageWithURL:(NSURL *)url {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}

我們跟進去看看再登,通過注釋可以得知這個方法的用途:

/**
 * ?根據(jù)url給imageView設置image尔邓,占位圖和各種自定義設置
 *
 * 使用異步下載和緩存
 *
 * @param url            圖片的url
 * @param placeholder    占位圖
 * @param options        下載圖片時的各種設置. @see SDWebImageOptions.
 * @param progressBlock  當圖片正在下載時會被回調(diào)到
 * @param completedBlock 當任務完成時會被回調(diào)到 。該block沒有返回值使用UIImage作為第一個參數(shù)
 *                       如果下載中出現(xiàn)錯誤UIIMage為nil并且第二個參數(shù)會包含NSError
 *                       第三個參數(shù)是一個枚舉(*原注釋這塊寫的是布爾值)霎冯,表示圖片是從本地緩存中還是網(wǎng)絡中取回的
 *                       第四個參數(shù)是原生的image url
 */
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;

//completedBlock铃拇,參數(shù)與注釋對應
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);

接著我們看代碼钞瀑,然后一步步分析:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    //取消當前UIImageView正在加載的圖片任務
    [self sd_cancelCurrentImageLoad];

    //相當于給當前UIImageView對象上綁定圖片url屬性
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    //如果options中沒有傳入SDWebImageDelayPlaceholder參數(shù)沈撞,則設置占位圖
    //這里出現(xiàn)了dispatch_main_async_safe,其實是SDWebImage定義的宏,其實就是將UI操作放入主線程中用的
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }

    if (url) {

        // 檢查是否打開了"會轉(zhuǎn)動的菊花"選項
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator]; //< 界面上會出現(xiàn)轉(zhuǎn)動的菊花
        }

        __weak __typeof(self)wself = self;
        //從方法名中可以猜出它是用來下載圖片用的雕什,目前只需要這么理解就好缠俺,后面章節(jié)會具體談到
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            [wself removeActivityIndicator]; //<將轉(zhuǎn)動的菊花從界面上移除
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
                //設置了SDWebImageAvoidAutoSetImage參數(shù)時显晶,默認不會將image添加進UIViewImage對象,而是放置到completedBlock中交由調(diào)用方自己處理壹士,比如做個濾鏡或者添加淡出淡入效果什么的
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    wself.image = image; //< 設置image
                    [wself setNeedsLayout];
                } else { //< 當image為nil
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;//< 此時再將占位圖設置進去
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        //保存本次operation磷雇,如果發(fā)生多次圖片請求加載可以用來取消
        //先取消當前UIImageView正在加載的任務,再保存operation
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        dispatch_main_async_safe(^{
            [self removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

這里先提幾個點:
1.在代碼中我們會發(fā)現(xiàn)有dispatch_main_async_safe這么一個神奇的東東躏救,其實它是SDWebImage定義的宏唯笙,將UI操作放入主線程中用的:

#define dispatch_main_async_safe(block)
     if ([NSThread isMainThread]) { //< 如果當前在主線程中
         block();
     } else { //< 不在主線程就將它放入主線程
         dispatch_async(dispatch_get_main_queue(), block);
     }

2.代碼中偶爾會出現(xiàn)objc_setAssociatedObject,簡單的說使用該技巧可以很方便的將變量動態(tài)綁定在該實例下盒使,原因在于Category中是不允許添加實例變量崩掘。

回到主題來,代碼在請求下載圖片前執(zhí)行了[self sd_cancelCurrentImageLoad]少办,從方法名上可以猜出它的大意“取消當前圖片的加載”苞慢,他是作什么用的呢,為什么在加載圖片前會需要用到取消這么一個方法英妓?帶著疑問我們繼續(xù)挽放,發(fā)現(xiàn)調(diào)用了另一個方法,看來這里只負責傳入對應的“key”

- (void)sd_cancelCurrentImageLoad {
    [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}

再跟進來我們可以看到具體的實現(xiàn)了

- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    //利用AssociatedObject維護的字典蔓纠,用于存放當前任務中的operation(圖片請求)
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    //key為"UIImageViewImageLoad"
    id operations = [operationDictionary objectForKey:key];

    if (operations) { //< 當前有正在執(zhí)行的operation辑畦,需要取消任務
        //多個operation的是gif(多幀),單個的是普通圖片
        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]; //< 取消
        }
        //刪除對應key的對象
        //每次對應UIView有一個圖片請求的任務時腿倚,都會設置對應的key航闺,所以可以根據(jù)這個key來判斷是否有正在執(zhí)行的任務
        [operationDictionary removeObjectForKey:key];
    }
}

看完上面這段代碼后,我們大致有了一個概念猴誊,同時也發(fā)現(xiàn)這兩段代碼的“key”是一樣的:

//取消當前UIImageView正在加載的圖片任務
[self sd_cancelCurrentImageLoad];
...
//保存本次operation潦刃,如果發(fā)生多次圖片請求加載可以用來取消
//先取消當前UIImageView正在加載的任務,再保存operation
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];

再回到剛才的疑問懈叹,舉個例子來說就能明白方法的意圖和具體流程了:

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 50, 50)];
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.example.com/1.png"] placeholderImage:nil];
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.example.com/2.png"] placeholderImage:nil];

一個imageView請求了兩張圖片乖杠,1.png 和 2.png,但我們只希望顯示 2.png澄成,所以需要取消 1.png的請求胧洒。原因有兩點:
1.在異步請求中(先后順序不定),有可能 1.png 會在 2.png 后面獲取到墨状,會覆蓋掉2.png
2.減少網(wǎng)絡請求卫漫,網(wǎng)絡請求是一個很耗時的操作

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肾砂,隨后出現(xiàn)的幾起案子列赎,更是在濱河造成了極大的恐慌,老刑警劉巖镐确,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件包吝,死亡現(xiàn)場離奇詭異饼煞,居然都是意外死亡,警方通過查閱死者的電腦和手機诗越,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門砖瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嚷狞,你說我怎么就攤上這事块促。” “怎么了床未?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵褂乍,是天一觀的道長。 經(jīng)常有香客問我即硼,道長逃片,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任只酥,我火速辦了婚禮褥实,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘裂允。我一直安慰自己损离,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布绝编。 她就那樣靜靜地躺著僻澎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪十饥。 梳的紋絲不亂的頭發(fā)上窟勃,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音逗堵,去河邊找鬼秉氧。 笑死,一個胖子當著我的面吹牛蜒秤,可吹牛的內(nèi)容都是我干的汁咏。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼作媚,長吁一口氣:“原來是場噩夢啊……” “哼攘滩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纸泡,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤漂问,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體级解,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡冒黑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年田绑,在試婚紗的時候發(fā)現(xiàn)自己被綠了勤哗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡掩驱,死狀恐怖芒划,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欧穴,我是刑警寧澤民逼,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站涮帘,受9級特大地震影響拼苍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜调缨,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一疮鲫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弦叶,春花似錦俊犯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至立莉,卻和暖如春绢彤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜓耻。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工杖虾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人媒熊。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓奇适,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芦鳍。 傳聞我的和親對象是個殘疾皇子嚷往,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,858評論 25 707
  • 技術無極限,從菜鳥開始柠衅,從源碼開始皮仁。 由于公司目前項目還是用OC寫的項目,沒有升級swift 所以暫時SDWebI...
    充滿活力的早晨閱讀 12,630評論 0 2
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件贷祈、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,066評論 4 62
  • 今天我們來聊聊“眼光”吧…… 話說趋急,在中國,家喻戶曉势誊,被認可最有眼光呜达,最有影響力的商人,非馬云是也粟耻。 大家認同嗎查近?...
    曹陽CY閱讀 262評論 0 0
  • 上一章:大漢王朝(下) [前言] 近來,項目的事情基本成型挤忙,業(yè)已走向正規(guī)...
    獨孤一鳴閱讀 667評論 56 41