最近研究了一下UITbleView中異步加載網(wǎng)絡(luò)圖片的問(wèn)題,iOS應(yīng)用經(jīng)常會(huì)看到這種界面。一個(gè)tableView上顯示一些標(biāo)題、詳情等內(nèi)容,在加上一張圖片。這里說(shuō)一下這種思路。
為了防止圖片多次下載,我們需要對(duì)圖片做緩存,緩存分為內(nèi)存緩存和沙盒緩存,我們當(dāng)然兩種都要實(shí)現(xiàn)。
由于tableViewCell是有重用機(jī)制的,也就是說(shuō),內(nèi)存中只有當(dāng)前可見(jiàn)的cell數(shù)目的實(shí)例,滑動(dòng)的時(shí)候,新顯示cell會(huì)重用被滑出的cell對(duì)象。這樣就存在一個(gè)問(wèn)題:
一般情況下在我們會(huì)在cellForRow方法里面設(shè)置cell的圖片數(shù)據(jù)源,也就是說(shuō)如果一個(gè)cell的imageview對(duì)象開(kāi)啟了一個(gè)下載任務(wù),這個(gè)時(shí)候該cell對(duì)象發(fā)生了重用,新的image數(shù)據(jù)源會(huì)開(kāi)啟另外的一個(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。
所以在我們向下滑動(dòng)tableview的時(shí)候我們需要手動(dòng)去取消掉下載操作,當(dāng)用戶停止滑動(dòng),再去執(zhí)行下載操作。SDWebImage采用的也是這種策略。
很簡(jiǎn)單我們只需要監(jiān)聽(tīng)ScrollView的代理方法(tableview繼承自Scrollview)。
/** * 當(dāng)用戶開(kāi)始拖拽表格時(shí)調(diào)用 */
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
// 暫停下載
[self.queue setSuspended:YES];
}
/** * 當(dāng)用戶停止拖拽表格時(shí)調(diào)用 */
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// 恢復(fù)下載
[self.queue setSuspended:NO];
}
而SDWebImage才用的就是這種方式,所以在運(yùn)行SDWebImage的demo的時(shí)候,可以看到,如果快速滑下去,然后又滑回來(lái)的話,圖片是過(guò)了一會(huì)才顯示出來(lái),這是因?yàn)榭焖倩瑒?dòng)的時(shí)候,舊數(shù)據(jù)源的下載任務(wù)被取消掉了。
/-------------------------------------------
下面介紹一下具體的思路。異步下載圖片我們用的是NSOperation,并且創(chuàng)建一個(gè)全局的queue來(lái)管理下載圖片的操作。
/** * 存放所有下載操作的隊(duì)列 */
@property (nonatomic,strong) NSOperationQueue* queue;
另外需要兩個(gè)字典operations、images
/** * 存放所有的下載操作(url是key,operation對(duì)象是value) */
@property (nonatomic,strong) NSMutableDictionary* operations;
/** * 存放所有下載完成的圖片,用于內(nèi)存緩存,同樣用Url為key */
@property (nonatomic,strong) NSMutableDictionary* images;
在把圖片顯示到Cell上之前
先判斷內(nèi)存中(images字典中)有沒(méi)有圖片,
如果有,則取出url對(duì)應(yīng)的圖片來(lái)顯示,
如果沒(méi)有,再去沙盒緩存中查看,當(dāng)然存到沙盒中都是NSData。
如果沙盒緩存中有,我們?nèi)〕鰧?duì)應(yīng)的數(shù)據(jù)給Cell去顯示
如果沙盒中也沒(méi)有圖片,我們先顯示占位圖片。再創(chuàng)建operation去執(zhí)行下載操作了。
當(dāng)然在創(chuàng)建operation之前,我們要判斷這個(gè)operation操作是否存在
這個(gè)時(shí)候就用到我們operations這個(gè)字典了
//取出當(dāng)前URL對(duì)應(yīng)的下載操作
NSBlockOperation* operation = self.operations[app.icon];
如果沒(méi)有下載操作,我們才需要真正的去創(chuàng)建operation執(zhí)行下載。
創(chuàng)建好下載操作之后應(yīng)該把該操作存放到全局隊(duì)列中去異步執(zhí)行,同時(shí)把操作放入operations字典中記錄下來(lái)。
//添加操作到隊(duì)列中
[self.queue addOperation:operation];
//添加到字典中
self.operations[app.icon] = operation;
下載完成之后:
把下載好的圖片放到內(nèi)存中、同時(shí)存到沙盒緩存中下面存放到沙盒中的代碼可以定義成宏,具體可以下載后面的demo
if (image) {
//防止下載失敗為空賦值造成崩潰
vc.images[app.icon] = image;
//下載完成的圖片存入沙盒中 // UIImage --> NSData --> File(文件) NSData* ImageData = UIImagePNGRepresentation(image);
NSString* CachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString* filePath = [CachesPath stringByAppendingPathComponent:[app.icon lastPathComponent]];
[ImageData writeToFile:filePath atomically:YES];
}
執(zhí)行完上面的操作之后回到主線程刷新表格,
從operations字典中移除下載操作(防止operations越來(lái)越大,同時(shí)保證下載失敗后,能重新下載)
//刷新當(dāng)前行的圖片數(shù)據(jù) self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]
這里我們不用[self.tableView reloadata],因?yàn)闀?huì)刷新整個(gè)cell,浪費(fèi)性能。
當(dāng)然如果你的下載操作里面需要做的事情很多的時(shí)候,可以考慮自定義operation。 由于我這里只是簡(jiǎn)單的下載小圖就沒(méi)有自定義operation了。注意要自己創(chuàng)建自動(dòng)釋放池。另外清理緩存的功能也沒(méi)有做有興趣的朋友可以自己嘗試下
渣渣代碼下載:https://github.com/hongfenglt/CellDownloadImage
至此基本實(shí)現(xiàn)思路已經(jīng)完成了,這時(shí)候你會(huì)發(fā)現(xiàn)其實(shí)真正在項(xiàng)目中,以上代碼并沒(méi)有什么卵用。因?yàn)檫@個(gè)操作,SDWebImgae已經(jīng)都封裝好,用一句代碼就搞定了,并且這句代碼代表的內(nèi)容比上面的操作更加完善?!雪n⊙‖∣°
以上內(nèi)容只是簡(jiǎn)單的優(yōu)化,想更深層去優(yōu)化操作可以參考下面這篇文章:http://www.itdecent.cn/p/3b2c95e1404f
文/勤奮的笨老頭(簡(jiǎn)書(shū)作者)原文鏈接:http://www.itdecent.cn/p/02ab2b74c451著作權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),并標(biāo)注“簡(jiǎn)書(shū)作者”。