最近研究了一下UITbleView中異步加載網絡圖片的問題,iOS應用經常會看到這種界面。
一個tableView上顯示一些標題、詳情等內容,在加上一張圖片。
這里說一下這種思路。
為了防止圖片多次下載,我們需要對圖片做緩存,緩存分為內存緩存于沙盒緩存,我們當然兩種都要實現。
由于tableViewCell是有重用機制的,也就是說,內存中只有當前可見的cell數目的實例,滑動的時候,新顯示cell會重用被滑出的cell對象。這樣就存在一個問題:
一般情況下在我們會在cellForRow方法里面設置cell的圖片數據源,也就是說如果一個cell的imageview對象開啟了一個下載任務,這個時候該cell對象發生了重用,新的image數據源會開啟另外的一個下載任務,由于他們關聯的imageview對象實際上是同一個cell實例的imageview對象,就會發生2個下載任務回調給同一個imageview對象。這個時候就有必要做一些處理,避免回調發生時,錯誤的image數據源刷新了UI。
所以在我們向下滑動tableview的時候我們需要手動去取消掉下載操作,當用戶停止滑動,再去執行下載操作。SDWebImage采用的也是這種策略。
很簡單我們只需要監聽ScrollView的代理方法(tableview繼承自Scrollview)。
/** * 當用戶開始拖拽表格時調用 */- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ // 暫停下載 [self.queue setSuspended:YES];}/** * 當用戶停止拖拽表格時調用 */- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ // 恢復下載 [self.queue setSuspended:NO];}而SDWebImage才用的就是這種方式,所以在運行SDWebImage的demo的時候,可以看到,如果快速滑下去,然后又滑回來的話,圖片是過了一會才顯示出來,這是因為快速滑動的時候,舊數據源的下載任務被取消掉了。
/-------------------------------------------
下面介紹一下具體的思路。
異步下載圖片我們用的是NSOperation,并且創建一個全局的queue來管理下載圖片的操作。
/** * 存放所有下載操作的隊列 */@PRoperty (nonatomic,strong) NSOperationQueue* queue;另外需要兩個字典operations、images
/** * 存放所有的下載操作(url是key,operation對象是value) */@property (nonatomic,strong) NSMutableDictionary* operations;/** * 存放所有下載完成的圖片,用于內存緩存,同樣用Url為key */@property (nonatomic,strong) NSMutableDictionary* images;在把圖片顯示到Cell上之前
先判斷內存中(images字典中)有沒有圖片,
如果有,則取出url對應的圖片來顯示,
如果沒有,再去沙盒緩存中查看,當然存到沙盒中都是NSData。
如果沙盒緩存中有,我們取出對應的數據給Cell去顯示
如果沙盒中也沒有圖片,我們先顯示占位圖片。再創建operation去執行下載操作了。
當然在創建operation之前,我們要判斷這個operation操作是否存在
這個時候就用到我們operations這個字典了
//取出當前URL對應的下載操作NSBlockOperation* operation = self.operations[app.icon];如果沒有下載操作,我們才需要真正的去創建operation執行下載。
創建好下載操作之后應該把該操作存放到全局隊列中去異步執行,同時吧操作放入operations字典中記錄下來。
//添加操作到隊列中 [self.queue addOperation:operation];//添加到字典中 self.operations[app.icon] = operation;下載完成之后:
把下載好的圖片放到內存中、同時存到沙盒緩存中
下面存放到沙盒中的代碼可以定義成宏,具體可以下載后面的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]; }執行完上面的操作之后回到主線程刷新表格,
從operations字典中移除下載操作(防止operations越來越大,同時保證下載失敗后,能重新下載)
//刷新當前行的圖片數據 self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];這里我們不用[self.tableView reloadata],因為會刷新整個cell,浪費性能。
當然如果你的下載操作里面需要做的事情很多的時候,可以考慮自定義operation。 由于我這里只是簡單的下載小圖就沒有自定義operation了。注意要自己創建自動釋放池。
新聞熱點
疑難解答