前言
昨天公司有新人過來面試坎怪,統(tǒng)一被問到了這個多圖下載的相關(guān)問題耸携,感覺有一個小伙子回答的挺完善文判,同時感覺有不少朋友在廣泛使用SDWebImage的同時忽略了內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)谈截,所以感覺有必要再次說上一說,希望對大家有所幫助
在實(shí)現(xiàn)多圖下載時我們需要考慮的問題
在我們平時下載圖片的時候都是使用的SDWebImage這個非常牛X的框架芜辕,其實(shí)它內(nèi)部已經(jīng)幫我們做了很多的細(xì)節(jié)處理尚骄,才保證了我們的圖片下載以及顯示包括程序順暢運(yùn)行块差,下面我們就來分析下自己實(shí)現(xiàn)完美的圖片下載會遇到什么問題呢
這里采用tabbleView每一個cell都需要下載顯示圖片為例來說明
1)首先我們需要考慮到圖片的緩存問題侵续,不然每次用戶下載完的圖片離開當(dāng)前頁面再回來就需要重復(fù)下載
- 圖片緩存第一步倔丈,就需要先弄一個內(nèi)存緩存,也就是自己創(chuàng)建一個數(shù)組状蜗,用來存放已經(jīng)下載下來的圖片
- 這樣每次用戶上拉需五、下拉或者從其他頁面回來再次顯示圖片的時候不至于重復(fù)下載
- 而且直接從內(nèi)存緩存里面取的話速度是非常快的轧坎,這樣不會造成卡頓也不會給用戶帶來不好的體驗
- 圖片緩存第二步宏邮,一個完美的圖片緩存,不可能需要用戶退出程序再次進(jìn)去后再去重新下載之前已經(jīng)下載過的圖片吧缸血,所以我們需要給用戶已經(jīng)下載過的圖片保存到沙盒里面
- 這里啰嗦的普及一個沙盒文件夾知識蜜氨,就知道我們需要把緩存的圖片放到哪一個文件夾下面了
1)Documents:
1-1 該目錄下面的數(shù)據(jù)在連接手機(jī)時會備份
1-2 蘋果官方不允許把下載的數(shù)據(jù)存放于該目錄下,否則審核直接被拒
2)Libriary:下面有2個子文件夾
2-1 caches:存放緩存文件
2-2 perference:該目錄用來存放偏好設(shè)置如登錄名密碼等等
3)Tmp
該文件夾下的文件會被隨時刪除捎泻,所以這個里面最好不要存放用戶的東西
*** 所以很明顯飒炎,我們需要把圖片存放于caches文件夾下
- 緊接著就是圖片如何存如何取的問題了,這里先不說圖片的下載笆豁,這是一個麻煩的過程郎汪,所以這里先說存取問題,也就是我們常說的二級緩存
- 每次圖片需要展示的時候闯狱,首先判斷圖片是否存在于當(dāng)前的內(nèi)存緩存中煞赢,也就是我們創(chuàng)建的存放圖片的數(shù)組,有的話就拿出來顯示
- 如果數(shù)組里面沒有我們需要顯示的圖片哄孤,那么我們就去沙盒緩存里面去取
- 如果沙盒緩存有照筑,從沙盒緩存里面取出顯示
- 然后將其存放于內(nèi)存緩存中,以便在程序不退出的時候用戶再次需要這張圖片的時候快速獲取并顯示
- 如果沙盒緩存也沒有的話录豺,這個時候就真的需要去網(wǎng)絡(luò)進(jìn)行下載朦肘,下載之后,不僅需要把圖片緩存到沙盒双饥,還需要把圖片保存到內(nèi)存緩存中
2)OK媒抠,解決了圖片緩存之后,那么就需要進(jìn)入最復(fù)雜的情況了咏花,就是如何處理下載圖片的時候產(chǎn)生的各種奇葩的問題
- 奇葩問題列表
- 下載圖片實(shí)質(zhì)上屬于耗時操作趴生,耗時操作我們是需要放在子線程中去處理的
- 我們需要弄一個字典去存放圖片的下載操作,字典的key就使用圖片的url昏翰,本質(zhì)上這個是唯一的苍匆,value自然就是圖片本身了
- 之所以要使用字典存放圖片的下載操作是因為,萬一用戶網(wǎng)速慢棚菊,這個時候圖片是不會馬上下載完成的浸踩,這個時候用戶萬一上下拖動頁面或者進(jìn)入其他頁面再次回到這個頁面,我們是需要判斷之前的下載操作是否存在的统求,如果存在就不需要再去創(chuàng)建了检碗,等待下載即可据块,如果不存在才需要創(chuàng)建的
- 還要考慮的就是存在下載操作但是下載失敗的情況,也就是image是空的折剃,這個時候我們需要去移除下載操作另假,以便用戶上拉下拉的時候重新創(chuàng)建下載任務(wù)重新下載圖片,不然下載失敗操作還在怕犁,這樣永遠(yuǎn)都無法重新下載了
- 下載創(chuàng)建回到主線程刷新UI的時候我們這里不能用tableView的reloadata方法边篮,原因是你如果使用了這個方法,那么用戶在圖片還沒有下載完成的時候上下來回拖動的話奏甫,cell會循環(huán)利用到其他地方戈轿,這樣圖片如果在這個時候下載好,就會把圖片顯示到錯誤的位置阵子,比如本來是第2行的圖片顯示到第5行去了
- 我們需要采用刷新指定行的方式凶杖,這樣無論你什么時間下載好圖片,你cell再循環(huán)利用款筑,無論當(dāng)前cell是否顯示在屏幕智蝠,我只要在下載好的時候去找到之前的indexPath去刷新,去給image賦值就行了奈梳,都會按照正確的位置去顯示
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
- 我們需要采用刷新指定行的方式凶杖,這樣無論你什么時間下載好圖片,你cell再循環(huán)利用款筑,無論當(dāng)前cell是否顯示在屏幕智蝠,我只要在下載好的時候去找到之前的indexPath去刷新,去給image賦值就行了奈梳,都會按照正確的位置去顯示
- 完善的下載還需要監(jiān)聽內(nèi)存警告杈湾,在收到內(nèi)存警告的時候移除內(nèi)存緩存,并將這個數(shù)組賦值nil攘须,并且移除下載隊列中的的操作
核心代碼
- 首先創(chuàng)建一個存放已下載圖片的內(nèi)存緩存字典-- iconsDict
- 再創(chuàng)建一個存放已創(chuàng)建下載操作的操作字典 -- operationsDict
- 創(chuàng)建一個非主隊列漆撞,可供全局操作,目的是將下載操作添加到隊列中于宙,讓下載可以異步執(zhí)行 -- downloadQueue
- iconsDict浮驳、operationsDict、downloadQueue需要多次被訪問到捞魁,需要使用到懶加載最好哦
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID ];
//這里我自己弄了一個模型至会,來給cell內(nèi)部子控件賦值,這里就給賦值操作簡單化了谱俭,沒有使用MVC
LBApp *model = self.appMarray[indexPath.row];
cell.textLabel.text = model.name;
NSString *downloadStr = [NSString stringWithFormat:@"已有%@下載",model.download];
cell.detailTextLabel.text =downloadStr;
// 先從內(nèi)存緩存中取出圖片
UIImage *image = [self.iconsDict objectForKey:model.icon];
if (image) {// 內(nèi)存中有圖片
cell.imageView.image = image;
}else {// 內(nèi)存中沒有圖片
// 獲得Caches文件夾
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 獲得文件名
NSString *filename = [app.icon lastPathComponent];
// 獲取文件全路徑
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
// 加載沙盒的文件數(shù)據(jù)
NSData *data = [NSData dataWithContentsOfFile:fullPath];
if (data) {// 直接利用沙盒中圖片
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
// 存到字典中
[self.iconsDict setObject:image forKey:model.icon];
}else {// 沒有的話就下載圖片
NSBlockOperation *download = nil;
//顯示占位圖片
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
download = self.operationsDict[model.icon];
if (download) {//如果存在下載操作奉件,什么也不做等待下載
}else {//創(chuàng)建下載操作
download = [NSBlockOperation blockOperationWithBlock:^{
//加載圖片
NSURL *url = [NSURL URLWithString:model.icon];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
if (!image) {//下載失敗
//下載失敗移除下載操作,以便用戶上拉下拉的時候重新創(chuàng)建下載任務(wù)重新下載圖片
[self.operationsDict removeObjectForKey:model.icon];
return ;
}
//下載成功
//image存進(jìn)內(nèi)存緩存,需要先存進(jìn)內(nèi)存然后再去刷新ui昆著,因為刷新ui會重新調(diào)用cellForRow方法县貌,這個時候如果內(nèi)存有圖片,就會直接從內(nèi)存加載然后顯示上去
[self.iconsDict setObject:image forKey:model.icon];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{//回到主線程刷新UI
//刷新指定行
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
}];
//image數(shù)據(jù)寫入磁盤緩存
[data writeToFile:fullPath atomically:YES];
//下載完成的時候移除下載操作
[self.operationsDict removeObjectForKey:model.icon];
}];
//將下載操作添加到隊列中凑懂,讓下載可以異步執(zhí)行
[self.downloadQueue addOperation:download];
//將下載操作添加到操作字典中煤痕,防止沒有下載完成上下拖拽的時候重復(fù)創(chuàng)建下載操作
self.operationsDict[model.icon] = download;
}
}
}
return cell;
}
結(jié)束語
這里這個大致的思路就說完畢了,大家如果有興趣可以多去研究研究SDWebImage內(nèi)部的實(shí)現(xiàn),對大家的幫助會很大哦摆碉,當(dāng)然存在不完善的地方希望大家可以共同討論學(xué)習(xí)哦祟敛,感謝您花時間閱讀,文字描述較多兆解,但是這些理論性的東西難免如此,非常抱歉