您好,登錄后才能下訂單哦!
ios教程,ios的干貨一直來不及給大家分享,小編也是一直在忙啊!今天給大家獻上ios:詳解iOS多圖下載的緩存機制
1. 需求點是什么?
這里所說的多圖下載,就是要在tableview的每一個cell里顯示一張圖片,而且這些圖片都需要從網上下載。
2. 容易遇到的問題
如果不知道或不使用異步操作和緩存機制,那么寫出來的代碼很可能會是這樣:
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;NSData *p_w_picpathData = [NSData dataWithContentsOfURL:app.url];
cell.p_w_picpathView.p_w_picpath = [UIImage p_w_picpathWithData:p_w_picpathData];
這樣寫有什么后果呢?
后果1:不可避免的卡頓(因為沒有異步下載操作)
dataWithContentsOfURL:是耗時操作,將其放在主線程會造成卡頓。如果圖片很多,圖片很大,而且網絡情況不好的話肯定會卡出翔!
后果2:同一圖片重復下載,耗費流量和系統開銷(因為沒有建立緩存機制)
由于沒有緩存機制,即使下載完成并顯示了當前cell的圖片,但是當該cell再一次需要顯示的時候還是會下載它所對應的圖片:耗費了下載流量,而且還導致重復操作。
很顯然,要達到Tableview滾動的如絲滑般的享受必須二者兼得才可以,具體怎么做呢?
3. 解決方案
1.先看一下解決方案的流程圖
要想快速看懂此圖,需要先了解該流程所需的所有數據源:
1. 圖片的URL:因為每張圖片對應的URL都是唯一的,所以我們可以通過它來建立圖片緩存和下載操作的緩存的鍵,以及拼接沙盒緩存的路徑字符串。
2. 圖片緩存(字典):存放于內存中;鍵為圖片的URL,值為UIImage對象。作用:讀取速度快,直接使用UIImage對象。
3. 下載操作緩存(字典):存放與內存中,鍵為圖片的URL,值為NSBlockOperation對象。作用:用來避免對于同一張圖片還要開啟多個下載線程。
4. 沙盒緩存(文件路徑對應NSData):存放于磁盤中,位于Cache文件夾內,路徑為“Cache/圖片URL的最后的部分”,值為NSData對象(將UIImage轉化為NSData才能寫入磁盤里)。作用:程序斷網,再次啟動也可以直接在磁盤中拿到圖片。
2. 再看一下解決方案的代碼
2.1圖片緩存,下載操作緩存,沙盒緩存路徑
/**
* 存放所有下載完的圖片
*/@property (nonatomic, strong) NSMutableDictionary *p_w_picpaths;/**
* 存放所有的下載操作(key是url,value是operation對象)
*/@property (nonatomic, strong) NSMutableDictionary *operations;/**
* 拼接Cache文件夾的路徑與url最后的部分,合并成唯一約定好的緩存路徑
*/#define CachedImageFile(url) [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[url lastPathComponent]]
2.2 圖片下載之前的查詢緩存部分:
//先從p_w_picpaths緩存中取出圖片url對應的UIImage
UIImage *p_w_picpath = self.p_w_picpaths[app.icon]; if (p_w_picpath) { //存在:說明圖片已經下載成功,并緩存成功)
cell.p_w_picpathView.p_w_picpath = p_w_picpath;
} else { // 不存在:說明圖片并未下載成功過,或者成功下載但是在p_w_picpaths里緩存失敗,需要在沙盒里尋找對于的圖片
// 獲得url對于的沙盒緩存路徑
NSString *file = CachedImageFile(app.icon); // 先從沙盒中取出圖片
NSData *data = [NSData dataWithContentsOfFile:file]; if (data) { //data不為空,說明沙盒中存在這個文件
cell.p_w_picpathView.p_w_picpath = [UIImage p_w_picpathWithData:data];
} else {// 反之沙盒中不存在這個文件
// 在下載之前顯示占位圖片
cell.p_w_picpathView.p_w_picpath = [UIImage p_w_picpathNamed:@"placeholder"];// 下載圖片
[self download:app.icon indexPath:indexPath];
}
}
2.3 圖片的下載部分:
/**
* 下載圖片
* @param p_w_picpathUrl 圖片的url
*/- (void)download:(NSString *)p_w_picpathUrl indexPath:(NSIndexPath *)indexPath
{ // 取出當前圖片url對應的下載操作(operation對象)
NSBlockOperation *operation = self.operations[p_w_picpathUrl]; if (operation) return; // 創建操作,下載圖片
__weak typeof(self) appsVc = self;
operation = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:p_w_picpathUrl]; NSData *data = [NSData dataWithContentsOfURL:url];// 下載
UIImage *p_w_picpath = [UIImage p_w_picpathWithData:data]; // NSData -> UIImage
// 回到主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ if (p_w_picpath) { // 如果存在圖片(下載完成),存放圖片到圖片緩存字典 中
appsVc.p_w_picpaths[p_w_picpathUrl] = p_w_picpath; //將圖片存入沙盒中
//1. 先將圖片轉化為NSData
NSData *data = UIImagePNGRepresentation (p_w_picpath); //2. 再生成緩存路徑
[data writeToFile:CachedImageFile(p_w_picpathUrl) atomically:YES];
} // 從字典中移除下載操作 (保證下載失敗后, 能重新下載)
[appsVc.operations removeObjectForKey:p_w_picpathUrl]; // 刷新當前表格,減少系統開銷
[appsVc.tableView reloadRowsAtIndexPaths:@ [indexPath] withRowAnimation:UITableViewRowAnimationNone];
}];
}]; // 添加下載操作到隊列中
[self.queue addOperation:operation]; // 將當前下載操作添加到下載操作緩存中 (為了解決重復下載)
self.operations[p_w_picpathUrl] = operation;
}
3. 有哪些點是值得注意的?
要說值得注意的地方,還是離不開對于緩存內容的添加和刪除操作。
3.1 關于圖片緩存:
很簡單,成功下載,拿到了圖片,就將圖片添加到圖片緩存中;下載失敗,什么都不做,反正沒有圖。在這種機制下,就沒有刪除緩存里某個圖片項的情況,因為圖片緩存永遠不會出現重復添加多個相同圖片的情況,緩存中只要有一張對應的圖,就直接拿去用了,不會去再下載了。
3.2 關于沙盒緩存:
同樣地,對于沙盒緩存也是一個道理:有圖就將其轉化為NSData,寫入磁盤,并對應唯一的路徑,沒有圖就不寫。所以即使是要下載相同的圖片,因為當前url對應的沙盒路徑已經存在文件了,所以直接拿就可以了,不會再下載。
但是!
下載操作緩存是不同的!
3.3 關于下載操作緩存
我們需要在下載回調完成后,立即將當前的下載操作從下載操作緩存中刪去!
因為要避免下載失敗后,無法再次下載的情況的發生!
為什么呢?
注意一下將下載操作加入到下載操作緩存的時機:
是在下載開始的那一刻而不是下載成功的那一刻!
如果在下載開始的那一刻加入到緩存中的話,這個緩存信息就包括兩個情況:下載成功和下載失敗:
如果未來下載成功了,那么我們就不會來到判斷當前下載操作是否在下載操作緩存這一步,在這之前直接就可以拿圖去用了,下載操作是否存在下載操作緩存里并沒有什么影響。
但是!如果未來下載失敗了,那就肯定不會有對應的圖片緩存和沙盒緩存,也就肯定會來到判斷當前的下載操作是否在下載操作緩存里這一步。不幸的是,因為沒有被刪去,它是存在的。存在的話就不做任何其他操作,放任自流,導致曾經下載失敗的圖片永遠不會再次下載。
忘了那段代碼了么?回看一下代碼(看我多好):
NSBlockOperation *operation = self.operations[p_w_picpathUrl]; if (operation) return;//轉身就走,毫不留情
因此,無論下載成功或是失敗,在圖片下載的回調里都要將當前的下載操作從下載操作隊列中移走:用來保證如果下載失敗了,就可以重新開啟對應的下載
操作進行下載,邏輯上更加嚴謹。
4. 最后的話
異步+緩存這兩個機制雙劍合璧的話會對程序新能帶來很大的改觀。這應該app開發進階的必經之路。
小碼哥講述的這套流程還算比較完整的了,更重要的還是學習其中的思想:
將緩存分級:內存緩存,沙盒緩存,下載操作緩存。
而且還要經常使用二分法,將我們的邏輯考慮得滴水不漏。
如果我們沒有認識到將下載操作添加到下載操作緩存的時機是包含下載成功和下載失敗兩個情況,那么就不會考慮到即時要將下載操作從下載操作緩存中刪去的操作,很容易引起bug。所以在以后的開發中,成功和失敗兩個情況都要考慮進去,也就是說有if一定要有else!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。