最近研究了一下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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * 當使用者開始拖拽表格時呼叫 */ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { // 暫停下載 [self.queue setSuspended:YES]; } /** * 當使用者停止拖拽表格時呼叫 */ - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { // 恢復下載 [self.queue setSuspended:NO]; } |
而SDWebImage才用的就是這種方式,所以在執行SDWebImage的demo的時候,可以看到,如果快速滑下去,然後又滑回來的話,圖片是過了一會才顯示出來,這是因為快速滑動的時候,舊資料來源的下載任務被取消掉了。
/——————————————-
下面介紹一下具體的思路。
非同步下載圖片我們用的是NSOperation,並且建立一個全域性的queue來管理下載圖片的操作。
1 2 3 4 |
/** * 存放所有下載操作的佇列 */ @property (nonatomic,strong) NSOperationQueue* queue; |
另外需要兩個字典operations、images
1 2 3 4 5 6 7 8 |
/** * 存放所有的下載操作(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這個字典了
1 2 |
//取出當前URL對應的下載操作 NSBlockOperation* operation = self.operations[app.icon]; |
如果沒有下載操作,我們才需要真正的去建立operation執行下載。
建立好下載操作之後應該把該操作存放到全域性佇列中去非同步執行,同時吧操作放入operations字典中記錄下來。
1 2 3 4 |
//新增操作到佇列中 [self.queue addOperation:operation]; //新增到字典中 self.operations[app.icon] = operation; |
下載完成之後:
把下載好的圖片放到記憶體中、同時存到沙盒快取中
下面存放到沙盒中的程式碼可以定義成巨集,具體可以下載後面的demo
1 2 3 4 5 6 7 8 9 10 11 12 |
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越來越大,同時保證下載失敗後,能重新下載)
1 2 |
//重新整理當前行的圖片資料 self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; |
這裡我們不用[self.tableView reloadata],因為會重新整理整個cell,浪費效能。
當然如果你的下載操作裡面需要做的事情很多的時候,可以考慮自定義operation。 由於我這裡只是簡單的下載小圖就沒有自定義operation了。注意要自己建立自動釋放池。
另外清理快取的功能也沒有做
有興趣的朋友可以自己嘗試下
渣渣程式碼下載:https://github.com/hongfenglt/CellDownloadImage
至此基本實現思路已經完成了,這時候你會發現其實真正在專案中,以上程式碼並沒有什麼卵用。因為這個操作,SDWebImgae已經都封裝好,用一句程式碼就搞定了,並且這句程式碼代表的內容比上面的操作更加完善。⊙﹏⊙‖∣°
以上內容只是簡單的優化,想更深層去優化操作可以參考下面這篇文章:http://ios.jobbole.com/82374