iOS多圖異步下載和緩存機(jī)制

奮斗的郅博

大量小圖顯示指的也是多圖下載旗们。我們?cè)诤芏囗?xiàng)目中會(huì)遇到UITableView需要去顯示一些標(biāo)題廷痘、詳情蔓涧、圖片等內(nèi)容。我們需要去加載圖片顯示笋额,有時(shí)候我們會(huì)為書寫簡(jiǎn)單選擇這種思路元暴。

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

這樣寫有什么后果呢?

  • 1.不可避免的卡頓(因?yàn)闆]有異步下載操作)
  • 2.dataWithContentsOfURL:是耗時(shí)操作,將其放在主線程會(huì)造成卡頓兄猩。如果圖片很多茉盏、很大,并且網(wǎng)絡(luò)情況不好的話肯定會(huì)卡.
  • 3.同一圖片重復(fù)下載枢冤,耗費(fèi)流量和系統(tǒng)開銷(因?yàn)闆]有建立緩存機(jī)制)由于沒有緩存機(jī)制鸠姨,即使下載完成并顯示了當(dāng)前cell的圖片,但是當(dāng)該cell再一次需要顯示的時(shí)候還是會(huì)下載它所對(duì)應(yīng)的圖片:耗費(fèi)了下載流量淹真,而且還導(dǎo)致重復(fù)操作讶迁。

怎么可以避免這些問(wèn)題呢?

1.解決方案流程圖


圖片顯示邏輯流程圖

注:該流程圖所需要的數(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ì)于同一張圖片還要開啟多個(gè)下載線程咱筛。
  4. 沙盒緩存(文件路徑對(duì)應(yīng)NSData):存放于磁盤中搓幌,位于Cache文件夾內(nèi)。值為NSData對(duì)象(將UIImage轉(zhuǎn)化為NSData才能寫入磁盤里)迅箩。作用:程序斷網(wǎng)溉愁,再次啟動(dòng)也可以直接在磁盤中拿到圖片。

異步+緩存管理圖片的相關(guān)實(shí)現(xiàn)代碼

//圖片緩存饲趋,下載操作緩存拐揭,沙盒緩存路徑
  /**
  * 存放所有下載完的圖片
  */@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]]

//圖片下載之前的查詢緩存部分:
  //先從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)開銷
  [appsVc.tableView reloadRowsAtIndexPaths:@ [indexPath] withRowAnimation:UITableViewRowAnimationNone];
  }];
  }]; // 添加下載操作到隊(duì)列中
  [self.queue addOperation:operation]; // 將當(dāng)前下載操作添加到下載操作緩存中 (為了解決重復(fù)下載)
  self.operations[imageUrl] = operation;
  }

UITableView滑動(dòng)過(guò)程圖片的處理

關(guān)于UITableView我們知道固蚤,tableViewCell是有重用機(jī)制的娘汞,也就是說(shuō),內(nèi)存中只有當(dāng)前可見的cell數(shù)目的實(shí)例夕玩,滑動(dòng)的時(shí)候你弦,新顯示cell會(huì)重用被滑出的cell對(duì)象。這樣就存在一個(gè)問(wèn)題:
一般情況下在我們會(huì)在cellForRow方法里面設(shè)置cell的圖片數(shù)據(jù)源燎孟,也就是說(shuō)如果一個(gè)cell的imageview對(duì)象開啟了一個(gè)下載任務(wù)禽作,這個(gè)時(shí)候該cell對(duì)象發(fā)生了重用,新的image數(shù)據(jù)源會(huì)開啟另外的一個(gè)下載任務(wù)缤弦,由于他們關(guān)聯(lián)的imageview對(duì)象實(shí)際上是同一個(gè)cell實(shí)例的imageview對(duì)象领迈,就會(huì)發(fā)生2個(gè)下載任務(wù)回調(diào)給同一個(gè)imageview對(duì)象。這個(gè)時(shí)候就有必要做一些處理碍沐,避免回調(diào)發(fā)生時(shí)狸捅,錯(cuò)誤的image數(shù)據(jù)源刷新了UI。如第三方的SDWebImage提供的UIImageView擴(kuò)展的解決方案就很實(shí)用:imageView對(duì)象會(huì)關(guān)聯(lián)一個(gè)下載列表(列表是給AnimationImages用的累提,這個(gè)時(shí)候會(huì)下載多張圖片)尘喝,當(dāng)tableview滑動(dòng),imageView重設(shè)數(shù)據(jù)源(url)時(shí)斋陪,會(huì)cancel掉下載列表中所有的任務(wù)朽褪,然后開啟一個(gè)新的下載任務(wù)置吓。這樣子就保證了只有當(dāng)前可見的cell對(duì)象的imageView對(duì)象關(guān)聯(lián)的下載任務(wù)能夠回調(diào),不會(huì)發(fā)生image錯(cuò)亂缔赠。同時(shí)衍锚,SDWebImage管理了一個(gè)全局下載隊(duì)列(在DownloadManager中),并發(fā)量設(shè)置為6.也就是說(shuō)如果可見cell的數(shù)目是大于6的,就會(huì)有部分下載隊(duì)列處于等待狀態(tài)嗤堰。而且戴质,在添加下載任務(wù)到全局的下載隊(duì)列中去的時(shí)候,SDWebImage默認(rèn)是采取LIFO策略的踢匣,具體是在添加下載任務(wù)的時(shí)候告匠,將上次添加的下載任務(wù)添加依賴為新添加的下載任務(wù)。這樣可以優(yōu)化性能不至于使其出現(xiàn)卡頓的情況(滑動(dòng)時(shí)候不進(jìn)行加載)离唬。

 [wself.downloadQueue addOperation:operation];
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [wself.lastAddedOperation addDependency:operation];
            wself.lastAddedOperation = operation;
        }

需要注意的部分

1.關(guān)于圖片緩存:
下載成功后专,拿到了圖片,就將圖片添加到圖片緩存中;下載失敗输莺,什么都不做(反正此時(shí)也沒有圖片)戚哎。在這種機(jī)制下,就沒有刪除緩存里某個(gè)圖片項(xiàng)的情況嫂用,因?yàn)閳D片緩存永遠(yuǎn)不會(huì)出現(xiàn)重復(fù)添加多個(gè)相同圖片的情況建瘫,緩存中只要有一張對(duì)應(yīng)的圖,就直接拿去用了尸折,不會(huì)再去加載。
2.關(guān)于沙盒緩存:
同樣地殷蛇,對(duì)于沙盒緩存也是一個(gè)道理:有圖就將其轉(zhuǎn)化為NSData实夹,寫入磁盤,并對(duì)應(yīng)唯一的路徑粒梦,沒有圖就不寫亮航。所以即使是要下載相同的圖片,因?yàn)楫?dāng)前url對(duì)應(yīng)的沙盒路徑已經(jīng)存在文件了匀们,所以直接拿就可以了缴淋,不會(huì)再下載。
3.關(guān)于下載操作緩存
我們需要在下載回調(diào)完成后泄朴,立即將當(dāng)前的下載操作從下載操作緩存中刪去(為了要避免下載失敗后重抖,無(wú)法再次下載的情況的發(fā)生)。由于將下載操作加入到下載操作緩存的時(shí)機(jī)是在下載開始的那一刻而不是下載成功的那一刻祖灰。如果在下載開始的那一刻加入到緩存中的話钟沛,這個(gè)緩存信息就包括兩個(gè)情況:下載成功和下載失敗:下載成功也就有相應(yīng)的圖片緩存和沙盒緩存局扶,假如下載失敗了恨统,那就肯定不會(huì)有對(duì)應(yīng)的圖片緩存和沙盒緩存叁扫,也就肯定會(huì)來(lái)到判斷當(dāng)前的下載操作是否在下載操作緩存里這一步。因?yàn)闆]有被刪去畜埋,它是存在的莫绣。導(dǎo)致曾經(jīng)下載失敗的圖片永遠(yuǎn)不會(huì)再次下載。
因此悠鞍,無(wú)論下載成功或是失敗对室,在圖片下載的回調(diào)里都要將當(dāng)前的下載操作從下載操作隊(duì)列中移走:用來(lái)保證如果下載失敗了,就可以重新開啟對(duì)應(yīng)的下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狞玛,一起剝皮案震驚了整個(gè)濱河市软驰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌心肪,老刑警劉巖锭亏,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異硬鞍,居然都是意外死亡慧瘤,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門固该,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)锅减,“玉大人,你說(shuō)我怎么就攤上這事伐坏≌唬” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵桦沉,是天一觀的道長(zhǎng)每瞒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)纯露,這世上最難降的妖魔是什么剿骨? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮埠褪,結(jié)果婚禮上浓利,老公的妹妹穿的比我還像新娘。我一直安慰自己钞速,他們只是感情好贷掖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著渴语,像睡著了一般羽资。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遵班,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天屠升,我揣著相機(jī)與錄音潮改,去河邊找鬼。 笑死腹暖,一個(gè)胖子當(dāng)著我的面吹牛汇在,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脏答,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼糕殉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了殖告?” 一聲冷哼從身側(cè)響起阿蝶,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黄绩,沒想到半個(gè)月后羡洁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爽丹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年筑煮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粤蝎。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡真仲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出初澎,到底是詐尸還是另有隱情秸应,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布碑宴,位于F島的核電站灸眼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏墓懂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一霉囚、第九天 我趴在偏房一處隱蔽的房頂上張望捕仔。 院中可真熱鬧,春花似錦盈罐、人聲如沸榜跌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钓葫。三九已至,卻和暖如春票顾,著一層夾襖步出監(jiān)牢的瞬間础浮,已是汗流浹背帆调。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豆同,地道東北人番刊。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像影锈,于是被迫代替她去往敵國(guó)和親芹务。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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

  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,150評(píng)論 30 470
  • 前不久做了一個(gè)生成快照的需求鸭廷,其中用到 SDWebImage 來(lái)下載圖片枣抱,在使用該框架的過(guò)程中也遇到了一些問(wèn)題,索...
    ShannonChenCHN閱讀 14,075評(píng)論 12 241
  • 簡(jiǎn)書博客已經(jīng)暫停更新辆床,想看更多技術(shù)博客請(qǐng)到: 掘金 :J_Knight_ 個(gè)人博客: J_Knight_ 個(gè)人公眾...
    J_Knight_閱讀 8,701評(píng)論 31 113
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)佳晶、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,107評(píng)論 4 62
  • 桃花已過(guò)季佛吓, 沉默宵晚, 不說(shuō), 但吻夏荷入夢(mèng)维雇, 醉了淤刃, 疼了, 夢(mèng)醒了 除去行云流水的慷慨吱型, 卸下開心的盔甲逸贾, 我還...
    小白菜哦哦閱讀 212評(píng)論 0 3