利用內存結構及多線程優化多圖片下載(IOS篇)


利用內存結構及多線程優化多圖片下載(IOS篇)

前言

下載地址, 后續發布, 請繼續關注本blog

在IOS中,我們常常遇到多圖片下載的問題。最簡單的解決方案是直接利用別人寫好的框架。但是這如同練武,只練外功而不練內功。 在這些框架中,SDWebImage這個框架是比較常用的框架,對於該框架的使用,不在這再做詳細介紹。主要從計算機的視角和多線程 引發的一些問題來分享下如何自己做,或者說SDWebImage大體上也是基於這種方式來做的。在這之前,有必要先說下一些操作系統的基本架構和原理。

內存結構

其實在操作系統中,所謂的內存結構,不是指我們電腦中的內存。在專業術語中,電腦中的內存稱為主存。而內存結構指的是由磁盤+主存+緩存 構成的結構。在這個構架中,從磁盤的速度比主存的速度慢,而主存的速度又比緩存的速度慢。這三種存儲物質也是由不同的材料所做成, 所以緩存的價格大於主存的價格,而主存又大於磁盤的價格。要不然你都可以把電腦磁盤替換成內存了,那將是十分的快,當然的保證你電腦是不斷電的。所以程序啟動的時候 ,都是從磁盤中讀取數據,到主存中完成整個程序的加載,這時候,程序就在主存中。

重點

同樣的道理,我們在做App的時候,對於圖片下載這種問題。我們深知,必須得使用多線程來下載圖片,然后另外一個線程來刷新界面。這才不會導致因為下載事件過長而引起的界面十分不流暢。同時,我們為了避免重復的下載圖片,為用戶節省流量,並且也為了提高圖片的加載速度。我們有必要利用內存結構的特點來解決這個問題。所以對於這種問題,我們主要的思路就是 1.將下載的圖片緩存到主存中開辟的一塊緩存圖片的空間。進行UI渲染的時候到緩存中取到對應的圖片,渲染UI界面。

判斷邏輯過程如下:

1.先判斷主存緩存中有沒有圖片,如果沒有進行第二步

2.判斷磁盤有沒有緩存的圖片,如果有將其加載進內存,並緩存到主存中的緩存圖片的位置。如果沒有進行第三步

3.從網絡中下載圖片,並緩存到內存和磁盤上。

4.應用程序需用用到圖片的時候,直接從內存中的緩存圖片的位置拿。

存在的問題:

A.第一個是需要用子線程來下載圖片,主線程進行渲染, 從而提高程序流暢性。

B.第二個是解決因為圖片過大或者圖片數據下載過慢時候,圖片還沒有下載完,還沒緩存到內存中時候。用戶不斷拖拽TableView,

由於UITableViewCell循環利用,使得在進行判斷1的時候,重復下載圖片。

C.第三個是將主存中的圖片緩存寫入磁盤在渲染UI之前,但是我們可以為期在開個線程讓兩個同時進行,提高程序的效率。

對於第一個問題,很好解決。請看代碼, 這是自定義cell中針對傳入模型數據進行的處理。只需關注該重點,想要測試程序自行 到我的github上下載,如果你覺得這個程序對你有學習價值,記得給個star。

因為不想重復的粘貼代碼,所以以下代碼是最終版本的核心代碼,但是為了說明問題。問題重現,所以請跟着我的步驟來,一步步的 打開被注釋的代碼,觀察效果。 現在請你忽略所有注釋的代碼,先搞懂這是為了解決問題A。

- (void)setApp:(SWPApp *)app {
    
    _app = app;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // 1.子線程下載數據

        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 磁盤無緩存則從網絡下載
            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.先判斷下載該圖片的操作是否已經執行過
    // 如果執行過, 那么圖片緩存中必定存在圖片.
   // if (!cache.operationCache[self.app.icon]) {
        static int i = 0;
        NSLog(@"---%d", i);
        data = isFilePath ? [NSData dataWithContentsOfFile: url]
                          : (i++, [NSData dataWithContentsOfURL: [NSURL URLWithString: url ]]);
        
        // 如果數據下載失敗
        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界面的流暢度問題,我們就需要利用內存結構來節約用戶流量和提高UI再次渲染的速度。

所以此時,還沒將圖片緩存到主存中,所以請看下面動態圖。再將第12,24行打開,再看第二種動態圖會發現,打印值只到16,也就是所只下載了 16次圖片。這就大大提高了的說明了能節約用戶流量和提高UI再次渲染的速度。。 如下圖(沒加入主存時候)

分析B: 接着我們來看問題B。也許這時候你覺得程序已經不存在問題了,確實,現在的程序是不存在問題了,但是可能會遇到問題。就是遇到一種十分 極端的情況,這種情況可以通過斷網來進行模擬。(模擬數據量過大,或者下載速度太慢,此時用戶不斷滾動TableView)會造成,因為圖片沒下載好,也就還沒緩存到主存,所以當要取圖片的時候,到主存對應的位置 去取,卻發現沒有,這時候,就會調用網絡下載,下載圖片,就造成了不斷重復的下載。 如下圖

解決B 這時候我們就需要某種標志來,標志該下載已經存在,不需要重新下載。所以我用了一個字典來映射各個下載圖片的操作,在下載操作執行前從字典中取出,判斷有沒有該操作,有則不重復下載。這是可以打開第52,和82行即可,觀察到效果。(記得打開網絡!) 如下圖

分析C: 其實C問題所起來很好解決,閱讀我的源代碼,你可以看到第75行是在當前線程中寫入數據到磁盤,這就造成了,要等待該寫入操作完成后才退出該函數,接着才將渲染任務交給主線程。但是寫入操作和渲染操作其實是可以同時進行的。所以我們可以在這里使用異步函數

解決C: 打開對應的注釋(72和77), 驗證就不在做了,可以自己打印時間觀察。

所以對於多圖片下載的問題我們主要是這么做: 1.通過多線程的方式,解決UI能流暢渲染,。 2.通過利用內存構架提高UI渲染的速度,並且解決了第一種圖片重復下載問題。 3.通過標記操作,實現同一下載互斥,解決UITableViewCell重用機制造成的第二種圖片重復下載問題。

具體判斷邏輯與細節:

1.先判斷主存緩存中有沒有圖片,如果沒有進行第二步

2.判斷磁盤有沒有緩存的圖片,如果有則直接加載進主存緩存中,並記錄該次操作,如果沒有進行第三步。

3.先判斷該下載操作是否存在,如果存在,則不進行下載操作。如果不存在進行第四步。

4.從網絡中下載圖片,並且判斷下載是否成功,如果成功下載,則記錄該次下載操作,實現互斥。再將圖片寫入主存緩存,並開啟另外一個線程將圖片寫入磁盤。

如果沒有下載成功或者從磁盤中沒有加載成功,則移除該次的下載標志, 解除該次下載互斥。

5.主線程直接從主存中的圖片緩存位置來圖片,渲染到UI界面。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM