邏輯如圖:
問(wèn)題總結(jié):
圖片重復(fù)下載,通過(guò)添加的打印可以很明顯的看出來(lái)呵曹,解決方式是把之前下載好的圖片保存起來(lái),因?yàn)閳D片和文字要對(duì)應(yīng)铐殃,所以采用字典的方式進(jìn)行存儲(chǔ)跨新。
1、添加內(nèi)存緩存解決圖片重復(fù)下載問(wèn)題
2赘被、添加內(nèi)存緩存存在問(wèn)題肖揣,需要用磁盤緩存(沙盒緩存)來(lái)補(bǔ)充
3、UI不流暢羊异,因?yàn)橄螺d圖片操作和刷新UI的操作都是在主線程中操作的彤断,解決方法把下載操作放到子線程中進(jìn)行操作。UI不會(huì)自動(dòng)刷新平道,當(dāng)我拖動(dòng)的時(shí)候才會(huì)刷新頁(yè)面巢掺,解決方式要使用代碼手動(dòng)刷新劲蜻。
因?yàn)橄螺d操作是異步的,會(huì)先把沒(méi)有圖標(biāo)的cell返回轧苫,此時(shí)因?yàn)閳D標(biāo)的frame為0疫蔓,所以之后即使圖片下載下來(lái),frame變?yōu)槠渌~尺寸衅胀,frame還是會(huì)一直為0滚躯,所以不會(huì)顯示嘿歌。如果我手動(dòng)刷新宙帝,系統(tǒng)會(huì)從新走一遍cellForRowAtIndexPath:方法募闲,到設(shè)置圖標(biāo)這一步時(shí),會(huì)直接把內(nèi)存中存在的圖片(此時(shí)圖片是有frame的)直接設(shè)置到cell中會(huì)顯示靴患。由于拖動(dòng)太快蚁廓,導(dǎo)致的重復(fù)下載的問(wèn)題厨幻。解決方式是定義一個(gè)操作緩存(字典)况脆,把之前的操作都保存起來(lái)批糟,因?yàn)樯厦娉霈F(xiàn)的原因本質(zhì)就是因?yàn)閎lockOperation重復(fù)添加到queue中了。
這個(gè)問(wèn)題是這樣的盛末,需要顯示的圖片會(huì)進(jìn)行下載操作否淤,但是一張圖片下載需要時(shí)間,如果圖片還沒(méi)下載下來(lái)檐嚣,我就拖動(dòng)了嚎京,將它移出屏幕隐解,結(jié)果就是這張圖片沒(méi)有下載完,如果這時(shí)候我又拖動(dòng)了帕涌,又要顯示這張圖片,因?yàn)樯弦淮芜€沒(méi)下載完架谎,所以內(nèi)存和磁盤里都沒(méi)有谷扣,所以會(huì)重復(fù)下載捎琐。解決方式是定義一個(gè)操作緩存(字典),把之前的操作都保存起來(lái)末秃,因?yàn)樯厦娉霈F(xiàn)的原因本質(zhì)就是因?yàn)閎lockOperation重復(fù)添加到queue中了籽御。cell的重用機(jī)制導(dǎo)致的cell圖標(biāo)展示數(shù)據(jù)錯(cuò)亂的問(wèn)題,解決方案铃将,如果要進(jìn)行下載圖片劲阎,先清空原來(lái)cell上的圖片鸠真,但是一般不會(huì)直接設(shè)置為nil,會(huì)采用占位圖片的方式來(lái)解決吠卷。
最終完成版:
/** 定義操作緩存屬性 */
@property (nonatomic, strong) NSMutableDictionary *operations;
// 操作緩存屬性懶加載實(shí)現(xiàn)
- (NSMutableDictionary *)operations{
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
// 設(shè)置圖標(biāo)的位置修改如下:
// 先去查看內(nèi)存緩存中該圖片有沒(méi)有被下載過(guò)撤嫩,如果有直接拿來(lái)用,如果沒(méi)有就去檢查磁盤緩存茴她,如果沒(méi)有磁盤緩存程奠,就保存一份到內(nèi)存瞄沙,設(shè)置圖片慌核,否則就直接下載垮卓。
UIImage *image = [self.images objectForKey:item.icon];
// 如果有值直接拿來(lái)直接使用
if(image){
cell.imageView.image = image;
NSLog(@"使用了內(nèi)存緩存中的圖片----%zd",indexPath.row);
}else{
// 沙盒緩存路徑獲取(磁盤緩存)
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 獲得圖片的名稱
NSString *imageName = [item.icon lastPathComponent];
// 拼接全路徑
NSString *fullPath = [cachePath stringByAppendingPathComponent:imageName];
// 檢查磁盤緩存
NSData *imageData = [NSData dataWithContentsOfFile:fullPath];
if (imageData) {
// 設(shè)置圖標(biāo)
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
NSLog(@"沙盒存儲(chǔ)----%zd",indexPath.row);
// 保存圖片到內(nèi)存緩存
[self.images setObject:image forKey:item.icon];
}else{
// 檢查圖片是否在操作緩存中進(jìn)行下載粟按,如果在下載就什么也不做灭将,如果不在就添加下載任務(wù)
NSBlockOperation *blockOperation = [self.operations objectForKey:item.icon];
if (blockOperation) {
}else{
// 防止cell重用導(dǎo)致的數(shù)據(jù)錯(cuò)亂 先設(shè)置cell 的 image為空
cell.imageView.image = [UIImage imageNamed:@"placeHolder.png"];
// 創(chuàng)建下載操作
blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:item.icon];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
NSLog(@"下載------%@",[NSThread currentThread]);
// 當(dāng)url地址不正確 image為空 容錯(cuò)處理
if (!image) {
// 為了下一次進(jìn)來(lái)的時(shí)候再次嘗試進(jìn)行圖片下載
[self.operations removeObjectForKey:item.icon];
return ;
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
NSLog(@"UI------%@",[NSThread currentThread]);
// 手動(dòng)刷新 刷新UITableView指定的行
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
}];
// 保存圖片到內(nèi)存緩存 用圖片的URL作為圖片的key 保證key唯一
[self.images setObject:image forKey:item.icon];
// 保存圖片到沙盒緩存(磁盤緩存)
[imageData writeToFile:fullPath atomically:YES];
// 下載操作完成后進(jìn)行移除操作
[self.operations removeObjectForKey:item.icon];
}];
// 添加下載操作到操作緩存中
[self.operations setObject:blockOperation forKey:item.icon];
// 添加下載操作到并發(fā)隊(duì)列中
[self.queue addOperation:blockOperation];
}
}
}
- 內(nèi)存問(wèn)題:將圖片保存在內(nèi)存中是很方便的事,圖片少的情況下肯定沒(méi)問(wèn)題捌朴,但是圖片多了就會(huì)內(nèi)存警告张抄,要做一下處理。
- (void)didReceiveMemoryWarning{
// 移除內(nèi)存緩存,這里不會(huì)影響界面顯示泽台,因?yàn)橛袕?qiáng)引用的關(guān)系矾缓。
[self.images removeAllObjects];
// 移除隊(duì)列中所有操作
[self.queue cancelAllOperations];
}
原貼寫的更清晰相近嗜闻,轉(zhuǎn)自:http://www.reibang.com/p/6edf6b9b2c1b