詳解 iOS 多圖下載的緩存機(jī)制


簡(jiǎn)書(shū)博客已經(jīng)暫停更新,想看更多技術(shù)博客請(qǐng)到:

  • 掘金 :J_Knight_
  • 個(gè)人博客: J_Knight_
  • 個(gè)人公眾號(hào):程序員維他命

做iOS開(kāi)發(fā)也有半年多了金赦,想想自己對(duì)一些第三方庫(kù)還只是停留在簡(jiǎn)單運(yùn)用的階段音瓷,感覺(jué)心慌慌的。于是決定用一個(gè)月的時(shí)間深入了解一些好的第三方庫(kù)夹抗。

第一個(gè)想到了SDWebImage绳慎,這個(gè)庫(kù)很不錯(cuò),幾乎每個(gè)iOS項(xiàng)目都會(huì)有它的影子漠烧,因?yàn)樗芡昝赖亟鉀Q了下載圖片并顯示的處理邏輯杏愤。那么深究它之前,筆者準(zhǔn)備先了解一下多圖下載的緩存機(jī)制已脓,因?yàn)樗蚐DWebImage的方案類(lèi)似珊楼。

有一個(gè)多圖緩存機(jī)制的教程是來(lái)自李明杰小碼哥的,筆者覺(jué)得講得挺不錯(cuò)的度液,于是就花了2個(gè)小時(shí)好好學(xué)習(xí)了一下厕宗。

Demo地址:multi_image_cache_and_download

1. 需求點(diǎn)是什么?


這里所說(shuō)的多圖下載堕担,就是要在tableview的每一個(gè)cell里顯示一張圖片,而且這些圖片都需要從網(wǎng)上下載已慢。

2. 容易遇到的問(wèn)題


如果不知道或不使用異步操作緩存機(jī)制,那么寫(xiě)出來(lái)的代碼很可能會(huì)是這樣:

cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
NSData *imageData = [NSData dataWithContentsOfURL:app.url];
cell.imageView.image = [UIImage imageWithData:imageData];

這樣寫(xiě)有什么后果呢霹购?

后果1:不可避免的卡頓(因?yàn)闆](méi)有異步下載操作)

dataWithContentsOfURL:是耗時(shí)操作佑惠,將其放在主線程會(huì)造成卡頓。如果圖片很多,圖片很大膜楷,而且網(wǎng)絡(luò)情況不好的話(huà)肯定會(huì)卡出翔旭咽!

后果2:同一圖片重復(fù)下載,耗費(fèi)流量和系統(tǒng)開(kāi)銷(xiāo)(因?yàn)闆](méi)有建立緩存機(jī)制)

由于沒(méi)有緩存機(jī)制赌厅,即使下載完成并顯示了當(dāng)前cell的圖片穷绵,但是當(dāng)該cell再一次需要顯示的時(shí)候還是會(huì)下載它所對(duì)應(yīng)的圖片:耗費(fèi)了下載流量,而且還導(dǎo)致重復(fù)操作察蹲。

很顯然请垛,要達(dá)到Tableview滾動(dòng)的如絲滑般的享受必須二者兼得才可以催训,具體怎么做呢洽议?

3. 解決方案


1. 先看一下解決方案的流程圖

小碼哥將他的解決方案在PPT里用流程圖畫(huà)了出來(lái),筆者覺(jué)得很不錯(cuò)漫拭,但是顏值略低(畢竟人家是一心搞技術(shù)亚兄,沒(méi)時(shí)間在意這些外在的東西),筆者理了理思路采驻,自己重新畫(huà)了一張(好看么审胚?):

多圖下載解決方案流程圖

要想快速看懂此圖,需要先了解該流程所需的所有數(shù)據(jù)源:

1. 圖片的URL:因?yàn)槊繌垐D片對(duì)應(yīng)的URL都是唯一的礼旅,所以我們可以通過(guò)它來(lái)建立圖片緩存下載操作的緩存的鍵膳叨,以及拼接沙盒緩存的路徑字符串。
2. 圖片緩存(字典):存放于內(nèi)存中痘系;鍵為圖片的URL菲嘴,值為UIImage對(duì)象。作用:讀取速度快汰翠,直接使用UIImage對(duì)象龄坪。
3. 下載操作緩存(字典):存放與內(nèi)存中,鍵為圖片的URL复唤,值為NSBlockOperation對(duì)象健田。作用:用來(lái)避免對(duì)于同一張圖片還要開(kāi)啟多個(gè)下載線程。
4. 沙盒緩存(文件路徑對(duì)應(yīng)NSData):存放于磁盤(pán)中佛纫,位于Cache文件夾內(nèi)妓局,路徑為“Cache/圖片URL的最后的部分”,值為NSData對(duì)象(將UIImage轉(zhuǎn)化為NSData才能寫(xiě)入磁盤(pán)里)呈宇。作用:程序斷網(wǎng)好爬,再次啟動(dòng)也可以直接在磁盤(pán)中拿到圖片。

2. 再看一下解決方案的代碼

2.1圖片緩存攒盈,下載操作緩存抵拘,沙盒緩存路徑

/**
 *  存放所有下載完的圖片
 */
@property (nonatomic, strong) NSMutableDictionary *images;

/**
 *  存放所有的下載操作(key是url,value是operation對(duì)象)
 */
@property (nonatomic, strong) NSMutableDictionary *operations;

/**
 *  拼接Cache文件夾的路徑與url最后的部分型豁,合并成唯一約定好的緩存路徑
 */
#define CachedImageFile(url) [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[url lastPathComponent]]

2.2 圖片下載之前的查詢(xún)緩存部分

    // 先從images緩存中取出圖片url對(duì)應(yīng)的UIImage
    UIImage *image = self.images[app.icon];
    if (image) { 

        // 存在:說(shuō)明圖片已經(jīng)下載成功僵蛛,并緩存成功)
        cell.imageView.image = image;

    } else { 

         // 不存在:說(shuō)明圖片并未下載成功過(guò)尚蝌,或者成功下載但是在images里緩存失敗,需要在沙盒里尋找對(duì)于的圖片

         // 獲得url對(duì)于的沙盒緩存路徑
        NSString *file = CachedImageFile(app.icon);
        
        // 先從沙盒中取出圖片
        NSData *data = [NSData dataWithContentsOfFile:file];

        if (data) {
    
            //data不為空充尉,說(shuō)明沙盒中存在這個(gè)文件
            cell.imageView.image = [UIImage imageWithData:data];

        } else {

             // 反之沙盒中不存在這個(gè)文件
             // 在下載之前顯示占位圖片
            cell.imageView.image = [UIImage imageNamed:@"placeholder"];
            
            // 下載圖片
            [self download:app.icon indexPath:indexPath];
        }
    }


2.3 圖片的下載部分


/**
 *  下載圖片
 *
 *  @param imageUrl 圖片的url
 */
- (void)download:(NSString *)imageUrl indexPath:(NSIndexPath *)indexPath
{
    // 取出當(dāng)前圖片url對(duì)應(yīng)的下載操作(operation對(duì)象)
    NSBlockOperation *operation = self.operations[imageUrl];
    if (operation) return;
    
    // 創(chuàng)建操作飘言,下載圖片
    __weak typeof(self) appsVc = self;
    operation = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:imageUrl];
        NSData *data = [NSData dataWithContentsOfURL:url]; // 下載
        UIImage *image = [UIImage imageWithData:data]; // NSData -> UIImage
        
        // 回到主線程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                     
            if (image) {
                // 如果存在圖片(下載完成),存放圖片到圖片緩存字典中
                appsVc.images[imageUrl] = image;
                
                //將圖片存入沙盒中
                //1. 先將圖片轉(zhuǎn)化為NSData
                NSData *data = UIImagePNGRepresentation(image);
                
                //2.  再生成緩存路徑            
                [data writeToFile:CachedImageFile(imageUrl) atomically:YES];
            }
            
            // 從字典中移除下載操作 (保證下載失敗后驼侠,能重新下載)
            [appsVc.operations removeObjectForKey:imageUrl];
            
            // 刷新當(dāng)前表格姿鸿,減少系統(tǒng)開(kāi)銷(xiāo)
            [appsVc.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];
    
    // 添加下載操作到隊(duì)列中
    [self.queue addOperation:operation];
    
    // 將當(dāng)前下載操作添加到下載操作緩存中 (為了解決重復(fù)下載)
    self.operations[imageUrl] = operation;
}

3. 有哪些點(diǎn)是值得注意的?

要說(shuō)值得注意的地方倒源,還是離不開(kāi)對(duì)于緩存內(nèi)容的添加和刪除操作苛预。

3.1 關(guān)于圖片緩存
很簡(jiǎn)單,成功下載笋熬,拿到了圖片热某,就將圖片添加到圖片緩存中;下載失敗胳螟,什么都不做昔馋,反正沒(méi)有圖。在這種機(jī)制下糖耸,就沒(méi)有刪除緩存里某個(gè)圖片項(xiàng)的情況秘遏,因?yàn)閳D片緩存永遠(yuǎn)不會(huì)出現(xiàn)重復(fù)添加多個(gè)相同圖片的情況,緩存中只要有一張對(duì)應(yīng)的圖嘉竟,就直接拿去用了邦危,不會(huì)去再下載了。

3.2 關(guān)于沙盒緩存
同樣地周拐,對(duì)于沙盒緩存也是一個(gè)道理:有圖就將其轉(zhuǎn)化為NSData铡俐,寫(xiě)入磁盤(pán),并對(duì)應(yīng)唯一的路徑妥粟,沒(méi)有圖就不寫(xiě)审丘。所以即使是要下載相同的圖片,因?yàn)楫?dāng)前url對(duì)應(yīng)的沙盒路徑已經(jīng)存在文件了勾给,所以直接拿就可以了滩报,不會(huì)再下載。

但是播急!
下載操作緩存是不同的脓钾!

3.3 關(guān)于下載操作緩存
我們需要在下載回調(diào)完成后,立即將當(dāng)前的下載操作從下載操作緩存中刪去桩警!
因?yàn)橐苊庀螺d失敗后可训,無(wú)法再次下載的情況的發(fā)生!

為什么呢?
注意一下將下載操作加入到下載操作緩存的時(shí)機(jī):
是在下載開(kāi)始的那一刻而不是下載成功的那一刻握截!

如果在下載開(kāi)始的那一刻加入到緩存中的話(huà)飞崖,這個(gè)緩存信息就包括兩個(gè)情況:下載成功和下載失敗:

  • 如果未來(lái)下載成功了谨胞,那么我們就不會(huì)來(lái)到判斷當(dāng)前下載操作是否在下載操作緩存這一步固歪,在這之前直接就可以拿圖去用了,下載操作是否存在下載操作緩存里并沒(méi)有什么影響胯努。

  • 但是牢裳!如果未來(lái)下載失敗了,那就肯定不會(huì)有對(duì)應(yīng)的圖片緩存和沙盒緩存叶沛,也就肯定會(huì)來(lái)到判斷當(dāng)前的下載操作是否在下載操作緩存里這一步蒲讯。不幸的是,因?yàn)闆](méi)有被刪去恬汁,它是存在的伶椿。存在的話(huà)就不做任何其他操作,放任自流,導(dǎo)致曾經(jīng)下載失敗的圖片永遠(yuǎn)不會(huì)再次下載。

忘了那段代碼了么塘娶?回看一下代碼(看我多好):

NSBlockOperation *operation = self.operations[imageUrl];
 if (operation) return;//轉(zhuǎn)身就走记某,毫不留情

因此,無(wú)論下載成功或是失敗旱捧,在圖片下載的回調(diào)里都要將當(dāng)前的下載操作從下載操作隊(duì)列中移走:用來(lái)保證如果下載失敗了独郎,就可以重新開(kāi)啟對(duì)應(yīng)的下載操作進(jìn)行下載,邏輯上更加嚴(yán)謹(jǐn)枚赡。

4. 最后的話(huà)


異步+緩存這兩個(gè)機(jī)制雙劍合璧的話(huà)會(huì)對(duì)程序新能帶來(lái)很大的改觀氓癌。這應(yīng)該app開(kāi)發(fā)進(jìn)階的必經(jīng)之路。

小碼哥講述的這套流程還算比較完整的了贫橙,更重要的還是學(xué)習(xí)其中的思想:

  1. 將緩存分級(jí):內(nèi)存緩存贪婉,沙盒緩存,下載操作緩存卢肃。
  1. 而且還要經(jīng)常使用二分法疲迂,將我們的邏輯考慮得滴水不漏。
    如果我們沒(méi)有認(rèn)識(shí)到將下載操作添加到下載操作緩存的時(shí)機(jī)是包含下載成功和下載失敗兩個(gè)情況莫湘,那么就不會(huì)考慮到即時(shí)要將下載操作從下載操作緩存中刪去的操作尤蒿,很容易引起bug。所以在以后的開(kāi)發(fā)中幅垮,成功和失敗兩個(gè)情況都要考慮進(jìn)去腰池,也就是說(shuō)有if一定要有else!

-------------------------------- 2018年7月16日更新 --------------------------------

注意注意!J竟演怎!

筆者在近期開(kāi)通了個(gè)人公眾號(hào),主要分享編程避乏,讀書(shū)筆記爷耀,思考類(lèi)的文章。

  • 編程類(lèi)文章:包括筆者以前發(fā)布的精選技術(shù)文章拍皮,以及后續(xù)發(fā)布的技術(shù)文章(以原創(chuàng)為主)歹叮,并且逐漸脫離 iOS 的內(nèi)容,將側(cè)重點(diǎn)會(huì)轉(zhuǎn)移到提高編程能力的方向上铆帽。
  • 讀書(shū)筆記類(lèi)文章:分享編程類(lèi)咆耿,思考類(lèi)心理類(lèi)爹橱,職場(chǎng)類(lèi)書(shū)籍的讀書(shū)筆記萨螺。
  • 思考類(lèi)文章:分享筆者平時(shí)在技術(shù)上生活上的思考愧驱。

因?yàn)楣娞?hào)每天發(fā)布的消息數(shù)有限制慰技,所以到目前為止還沒(méi)有將所有過(guò)去的精選文章都發(fā)布在公眾號(hào)上,后續(xù)會(huì)逐步發(fā)布的组砚。

而且因?yàn)楦鞔蟛┛推脚_(tái)的各種限制吻商,后面還會(huì)在公眾號(hào)上發(fā)布一些短小精干,以小見(jiàn)大的干貨文章哦~

掃下方的公眾號(hào)二維碼并點(diǎn)擊關(guān)注糟红,期待與您的共同成長(zhǎng)~

公眾號(hào):程序員維他命
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末艾帐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盆偿,更是在濱河造成了極大的恐慌柒爸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事扭,死亡現(xiàn)場(chǎng)離奇詭異捎稚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)句旱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)阳藻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人谈撒,你說(shuō)我怎么就攤上這事腥泥。” “怎么了啃匿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蛔外,是天一觀的道長(zhǎng)蛆楞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)夹厌,這世上最難降的妖魔是什么豹爹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮矛纹,結(jié)果婚禮上臂聋,老公的妹妹穿的比我還像新娘。我一直安慰自己或南,他們只是感情好孩等,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著采够,像睡著了一般肄方。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹬癌,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天权她,我揣著相機(jī)與錄音,去河邊找鬼逝薪。 笑死隅要,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翼闽。 我是一名探鬼主播拾徙,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼感局!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起暂衡,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤询微,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后狂巢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體撑毛,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年唧领,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了藻雌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斩个,死狀恐怖胯杭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情受啥,我是刑警寧澤做个,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布鸽心,位于F島的核電站,受9級(jí)特大地震影響居暖,放射性物質(zhì)發(fā)生泄漏顽频。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一太闺、第九天 我趴在偏房一處隱蔽的房頂上張望糯景。 院中可真熱鬧,春花似錦省骂、人聲如沸蟀淮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)灭贷。三九已至,卻和暖如春略贮,著一層夾襖步出監(jiān)牢的瞬間甚疟,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工逃延, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留览妖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓揽祥,卻偏偏與公主長(zhǎng)得像讽膏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拄丰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • 大量小圖顯示指的也是多圖下載府树。我們?cè)诤芏囗?xiàng)目中會(huì)遇到UITableView需要去顯示一些標(biāo)題、詳情料按、圖片等內(nèi)容奄侠。我...
    奮斗的郅博閱讀 2,887評(píng)論 0 5
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,139評(píng)論 30 470
  • iOS開(kāi)發(fā)系列--網(wǎng)絡(luò)開(kāi)發(fā) 概覽 大部分應(yīng)用程序都或多或少會(huì)牽扯到網(wǎng)絡(luò)開(kāi)發(fā),例如說(shuō)新浪微博载矿、微信等垄潮,這些應(yīng)用本身可...
    lichengjin閱讀 3,657評(píng)論 2 7
  • 前不久做了一個(gè)生成快照的需求,其中用到 SDWebImage 來(lái)下載圖片闷盔,在使用該框架的過(guò)程中也遇到了一些問(wèn)題弯洗,索...
    ShannonChenCHN閱讀 14,066評(píng)論 12 241
  • 前幾天看到欒老師的博客,大致是08-12年逢勾,五年的時(shí)間牡整,這期間欒老師一個(gè)人寫(xiě)著博客和隨想,沒(méi)有評(píng)論敏沉,每一篇的瀏覽數(shù)...
    育種數(shù)據(jù)分析之放飛自我閱讀 311評(píng)論 0 0