下載地址, 后續(xù)發(fā)布, 請繼續(xù)關注本blog
在IOS中,我們常常遇到多圖片下載的問題。最簡單的解決方案是直接利用別人寫好的框架。但是這如同練武,只練外功而不練內功。在這些框架中,SDWebImage這個框架是比較常用的框架,對于該框架的使用,不在這再做詳細介紹。主要從計算機的視角和多線程引發(fā)的一些問題來分享下如何自己做,或者說SDWebImage大體上也是基于這種方式來做的。在這之前,有必要先說下一些操作系統(tǒng)的基本架構和原理。
其實在操作系統(tǒng)中,所謂的內存結構,不是指我們電腦中的內存。在專業(yè)術語中,電腦中的內存稱為主存。而內存結構指的是由磁盤+主存+緩存構成的結構。在這個構架中,從磁盤的速度比主存的速度慢,而主存的速度又比緩存的速度慢。這三種存儲物質也是由不同的材料所做成,所以緩存的價格大于主存的價格,而主存又大于磁盤的價格。要不然你都可以把電腦磁盤替換成內存了,那將是十分的快,當然的保證你電腦是不斷電的。所以程序啟動的時候,都是從磁盤中讀取數(shù)據(jù),到主存中完成整個程序的加載,這時候,程序就在主存中。
重點:
同樣的道理,我們在做App的時候,對于圖片下載這種問題。我們深知,必須得使用多線程來下載圖片,然后另外一個線程來刷新界面。這才不會導致因為下載事件過長而引起的界面十分不流暢。同時,我們?yōu)榱吮苊庵貜偷南螺d圖片,為用戶節(jié)省流量,并且也為了提高圖片的加載速度。我們有必要利用內存結構的特點來解決這個問題。所以對于這種問題,我們主要的思路就是1.將下載的圖片緩存到主存中開辟的一塊緩存圖片的空間。進行UI渲染的時候到緩存中取到對應的圖片,渲染UI界面。
判斷邏輯過程如下:
1.先判斷主存緩存中有沒有圖片,如果沒有進行第二步
2.判斷磁盤有沒有緩存的圖片,如果有將其加載進內存,并緩存到主存中的緩存圖片的位置。如果沒有進行第三步
3.從網(wǎng)絡中下載圖片,并緩存到內存和磁盤上。
4.應用程序需用用到圖片的時候,直接從內存中的緩存圖片的位置拿。
存在的問題:
A.第一個是需要用子線程來下載圖片,主線程進行渲染, 從而提高程序流暢性。
B.第二個是解決因為圖片過大或者圖片數(shù)據(jù)下載過慢時候,圖片還沒有下載完,還沒緩存到內存中時候。用戶不斷拖拽TableView,
由于UITableViewCell循環(huán)利用,使得在進行判斷1的時候,重復下載圖片。
C.第三個是將主存中的圖片緩存寫入磁盤在渲染UI之前,但是我們可以為期在開個線程讓兩個同時進行,提高程序的效率。
對于第一個問題,很好解決。請看代碼, 這是自定義cell中針對傳入模型數(shù)據(jù)進行的處理。只需關注該重點,想要測試程序自行到我的github上下載,如果你覺得這個程序對你有學習價值,記得給個star。
因為不想重復的粘貼代碼,所以以下代碼是最終版本的核心代碼,但是為了說明問題。問題重現(xiàn),所以請跟著我的步驟來,一步步的打開被注釋的代碼,觀察效果。現(xiàn)在請你忽略所有注釋的代碼,先搞懂這是為了解決問題A。
- (void)setApp:(SWPApp *)app { _app = app; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 1.子線程下載數(shù)據(jù) SWPCache * cache = [SWPCache sharedInstance]; // 1.1內存無緩存 // if ( cache.imageCache[app.icon] == nil ) { NSString * folderPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString * filePath = [folderPath stringByAppendingPathComponent:[app.icon lastPathComponent]]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath: filePath]; if (!fileExists) [self loadImageWithURLOrFilePath:app.icon isFilePath: NO]; // 1.2 磁盤無緩存則從網(wǎng)絡下載 else [self loadImageWithURLOrFilePath:filePath isFilePath: YES]; // 1.3 磁盤有緩存, 直接加載進內存中的緩存 // } // [NSThread sleepForTimeInterval: 1]; // 2.主線程渲染cell的UI dispatch_async(dispatch_get_main_queue(), ^{ self.textLabel.text = app.name; self.imageView.image = cache.imageCache[app.icon]; self.detailTextLabel.text = app.download; }); }); }- (void)loadImageWithURLOrFilePath:(NSString *)url isFilePath:(BOOL)isFilePath { SWPCache * cache = [SWPCache sharedInstance]; NSData * data = nil; // 1.先判斷下載該圖片的操作是否已經(jīng)執(zhí)行過 // 如果執(zhí)行過, 那么圖片緩存中必定存在圖片. // if (!cache.OperationCache[self.app.icon]) { static int i = 0; NSLog(@"---%d", i); data = isFilePath ? [NSData dataWithContentsOfFile: url] : (i++, [NSData dataWithContentsOfURL: [NSURL URLWithString: url ]]); // 如果數(shù)據(jù)下載失敗 if (!data) { [cache.operationCache removeObjectForKey: self.app.icon]; } else { UIImage * image = [UIImage imageWithData: data]; cache.imageCache[self.app.icon] = image; cache.operationCache[self.app.icon] = [NSNumber numberWithBool: true]; // if (!isFilePath) { // 1.為讓其一邊顯示一邊寫入 //dispatch_async(dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [data writeToFile:url atomically: YES]; // }); // } } // } }
解決完UI界面的流暢度問題,我們就需要利用內存結構來節(jié)約用戶流量和提高UI再次渲染的速度。
所以此時,還沒將圖片緩存到主存中,所以請看下面動態(tài)圖。再將第12,24行打開,再看第二種動態(tài)圖會發(fā)現(xiàn),打印值只到16,也就是所只下載了16次圖片。這就大大提高了的說明了能節(jié)約用戶流量和提高UI再次渲染的速度。。如下圖(沒加入主存時候)
分析B: 接著我們來看問題B。也許這時候你覺得程序已經(jīng)不存在問題了,確實,現(xiàn)在的程序是不存在問題了,但是可能會遇到問題。就是遇到一種十分極端的情況,這種情況可以通過斷網(wǎng)來進行模擬。(模擬數(shù)據(jù)量過大,或者下載速度太慢,此時用戶不斷滾動TableView)會造成,因為圖片沒下載好,也就還沒緩存到主存,所以當要取圖片的時候,到主存對應的位置去取,卻發(fā)現(xiàn)沒有,這時候,就會調用網(wǎng)絡下載,下載圖片,就造成了不斷重復的下載。如下圖
解決B這時候我們就需要某種標志來,標志該下載已經(jīng)存在,不需要重新下載。所以我用了一個字典來映射各個下載圖片的操作,在下載操作執(zhí)行前從字典中取出,判斷有沒有該操作,有則不重復下載。這是可以打開第52,和82行即可,觀察到效果。(記得打開網(wǎng)絡!)如下圖
分析C: 其實C問題所起來很好解決,閱讀我的源代碼,你可以看到第75行是在當前線程中寫入數(shù)據(jù)到磁盤,這就造成了,要等待該寫入操作完成后才退出該函數(shù),接著才將渲染任務交給主線程。但是寫入操作和渲染操作其實是可以同時進行的。所以我們可以在這里使用異步函數(shù)
解決C:打開對應的注釋(72和77), 驗證就不在做了,可以自己打印時間觀察。
所以對于多圖片下載的問題我們主要是這么做:1.通過多線程的方式,解決UI能流暢渲染,。2.通過利用內存構架提高UI渲染的速度,并且解決了第一種圖片重復下載問題。3.通過標記操作,實現(xiàn)同一下載互斥,解決UITableViewCell重用機制造成的第二種圖片重復下載問題。
具體判斷邏輯與細節(jié):
1.先判斷主存緩存中有沒有圖片,如果沒有進行第二步
2.判斷磁盤有沒有緩存的圖片,如果有則直接加載進主存緩存中,并記錄該次操作,如果沒有進行第三步。
3.先判斷該下載操作是否存在,如果存在,則不進行下載操作。如果不存在進行第四步。
4.從網(wǎng)絡中下載圖片,并且判斷下載是否成功,如果成功下載,則記錄該次下載操作,實現(xiàn)互斥。再將圖片寫入主存緩存,并開啟另外一個線程將圖片寫入磁盤。
如果沒有下載成功或者從磁盤中沒有加載成功,則移除該次的下載標志, 解除該次下載互斥。
5.主線程直接從主存中的圖片緩存位置來圖片,渲染到UI界面。
新聞熱點
疑難解答