根據SDWebImage框架總結tableView中網絡圖片異步下載可能會遇到的問題

梳理一下,在開發中利用SDWebImage下載圖片 ,這個框架會幫我們做什么事情。

這里自己寫代碼來實現解決所有的問題。

項目準備:

  • 1.首先創建數據源數組

      @implementation ViewController {
    
         /// 數據源數組
         NSArray *_appsList;
    
     }
    
     -(void)viewDidLoad {
         [super viewDidLoad];
                     
         [self loadJsonData];
      }
    
  • 2.利用第三方框架 AFNetworking 獲取網絡數據

      ///定義獲取JSON的主方法
      -(void)loadJsonData{
      
      //1、創建網絡請求管理者
      AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    
      //2、獲取
    
       [manager GET:@"https://raw.githubusercontent.com/lcy237777480/FYLoadImage/ master/apps.json" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSArray  * responseObject) {
      //請求網絡執行 回調(成功/失敗)都是在主線程
      NSLog(@"%@   %@ \n %@",[responseObject class],responseObject,[NSThread currentThread]);
      
      //responseObject就是獲取到的json數據
      //1、遍歷數據數組字典轉模型
      //4、創建可變數組用來保存模型
      NSMutableArray *modelsArr = [NSMutableArray arrayWithCapacity:responseObject.count];
      
      [responseObject enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
             //2、創建模型類
          //3、賦值
          FYAppModel *model = [FYAppModel appWithDict:obj];
          
          [modelsArr addObject:model];
      }];
      _appsArrM = modelsArr.copy;
      //網絡請求是耗時操作,拿到數據一定要reloadData
      [self.tableView reloadData];
    
       } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
      
       }];
      }
    
  • 3.實現tableView的數據源方法

      #pragma mark - 數據源方法
      -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
      
              return _appsArrM.count;
          }
      -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
      
           UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"reuseCellID" forIndexPath:indexPath];
           FYAppModel *model = _appsArrM[indexPath.row];
    
          // 給cell的子控件賦值
           cell.textLabel.text = model.name;
          cell.detailTextLabel.text = model.download;
    
          //利用SDWebImage框架 下載圖片
           [cell.imageView sd_setImageWithURL:[NSURL URLWithString:model.icon]];
    
          return cell;
      }
    

項目準備完畢


接下來的實現不再用SDWebImage,自己實現NSBlockOperation異步下載圖片,看看我們遇到了什么問題,也就是他幫助我們做了什么。

增加全局隊列
     @implementation ViewController {

        /// 數據源數組
        NSArray *_appsList;
        /// 全局隊列
        NSOperationQueue *_queue;
        
    }

實例化隊列

-(void)viewDidLoad {
        [super viewDidLoad];
        // 實例化隊列
        _queue = [[NSOperationQueue alloc] init];
        
        [self loadJsonData];
}

問題1 : 列表顯示出來后,并不顯示圖片,來回滾動cell或者點擊cell ,圖片才會顯示。

不顯示圖片
不顯示圖片

解決辦法 : 自定義cell

修改數據源方法
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    APPCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppsCell" forIndexPath:indexPath];

    // 獲取cell對應的數據模型
     AppsModel *app = _appsList[indexPath.row];

    // 給cell傳入模型對象
     cell.app = app;

    #**pragma mark - NSBlockOperation實現圖片的異步下載**
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    
    // 模擬網絡延遲
    [NSThread sleepForTimeInterval:0.2];
    
    // URL
    NSURL *URL = [NSURL URLWithString:app.icon];
    // data
    NSData *data = [NSData dataWithContentsOfURL:URL];
    // image
    UIImage *image = [UIImage imageWithData:data];
    
    // 圖片下載完成之后,回到主線程更新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        cell.iconImageView.image = image;
    }];
}];

     // 把操作添加到隊列
    [_queue addOperation:op];

        return cell;
    }

原理:

1.cell上的系統默認的子控件都是懶加載上去的

2.在返回cell之前,如果沒有給cell上的默認的子控件賦值,那么這個默認的子控件就不會加載到cell上;

3.跟cell做交互(點擊)時,默認會自動調用layoutSubViews方法,重新布局了子控件。

問題2 : 當有網絡延遲時,來回滾動cell,會出現cell上圖片的閃動;因為cell有復用

圖片的閃動
圖片的閃動

解決辦法 : 占位圖

 // 在圖片下載之前,先設置占位圖
cell.iconImageView.image = [UIImage imageNamed:@"user_default"];
添加站位圖后
添加站位圖后

問題3 : 圖片每次展示,都要重新下載,用戶流量流失快

解決辦法 : 設計內存緩存策略 (字典)

  • 3.1增加圖片緩存池

       @implementation ViewController {
    
          /// 數據源數組
          NSArray *_appsList;
          /// 全局隊列
          NSOperationQueue *_queue;
          /// 圖片緩存池
          NSMutableDictionary *_imagesCache;
      }
    
  • 3.2實例化圖片緩存池

      -(void)viewDidLoad {
          [super viewDidLoad];
          // 實例化隊列
          _queue = [[NSOperationQueue alloc] init];
          // 實例化圖片緩存池
          _imagesCache = [[NSMutableDictionary alloc] init];
    
          [self loadJsonData];
      }
    
  • 3.3 在cell的數據源方法中向緩存池中獲取圖片

      // 在建立下載操作之前,判斷要下載的圖片在圖片緩存池里面有沒有
      UIImage *memImage = [_imagesCache objectForKey:app.icon];
      //如果獲取到圖片
      if (memImage) {
           NSLog(@"從內存中加載...%@",app.name);
           //賦值
           cell.iconImageView.image = memImage;
          //直接返回,不執行后續操作
          return cell;
    }
    
  • 3.4在異步下載圖片的時候,將下載的圖片放入圖片緩存池中

      NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"從網絡中加載...%@",app.name);
      // 模擬網絡延遲
      [NSThread sleepForTimeInterval:0.2];
      
      // URL
      NSURL *URL = [NSURL URLWithString:app.icon];
      // data
      NSData *data = [NSData dataWithContentsOfURL:URL];
      // image
      UIImage *image = [UIImage imageWithData:data];
      
      // 圖片下載完成之后,回到主線程更新UI
      [[NSOperationQueue mainQueue] addOperationWithBlock:^{
          cell.iconImageView.image = image;
          
          // 把圖片保存到圖片緩存池
          if (image != nil) {
              [_imagesCache setObject:image forKey:app.icon];
                }
           }];
      }];
    
建立圖片緩存,避免重復下載
建立圖片緩存,避免重復下載

問題4 : 當有網絡延遲時,滾動cell會出現圖片錯行的問題

cell錯行
cell錯行

解決辦法 : 刷新對應的行

 // 圖片異步下載完成之后,刷新對應的行,并且不要動畫(偷偷的,不讓用戶發現)
 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

問題5 : 當有網絡延遲時,來回滾動cell,會重復建立下載操作

[站外圖片上傳中……(6)]

解決辦法 : 操作緩存池 (字典)

  • 5.1建立操作緩存池

     @implementation ViewController {
         /// 數據源數組
         NSArray *_appsList;
         /// 全局隊列
         NSOperationQueue *_queue;
         /// 圖片緩存池
         NSMutableDictionary *_imagesCache;
         /// 操作緩存池
         NSMutableDictionary *_OPCache;
     }
    
  • 5.2實例化

     _OPCache = [[NSMutableDictionary alloc] init];
    
  • 5.3模擬網絡延遲

     // 在建立下載操作之前,判斷下載操作是否存在
      if ([_OPCache objectForKey:app.icon] != nil) {
           NSLog(@"正在下載中...%@",app.name);
           return cell;
       }
    
     NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
      ***************************************
     // 模擬網絡延遲 : 讓屏幕之外的圖片的下載延遲時間比較長
     if (indexPath.row > 9) {
         [NSThread sleepForTimeInterval:15.0];
     }
     ***************************************
     NSURL *URL = [NSURL URLWithString:app.icon];
     NSData *data = [NSData dataWithContentsOfURL:URL];
     UIImage *image = [UIImage imageWithData:data];
     
     // 圖片下載完成之后,回到主線程更新UI
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
         if (image != nil) {
             [_imagesCache setObject:image forKey:app.icon];
             
             [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
         }
          ***************************************
         // 圖片下載完成之后,需要把操作緩存池的操作移除
         [_OPCache removeObjectForKey:app.icon];
          ***************************************
         }];
      }];
    
      ***************************************
      // 把下載操作添加到操作緩存池
     [_OPCache setObject:op forKey:app.icon];
      ***************************************
      // 把操作添加到隊列
      [_queue addOperation:op];
    
     return cell;
     }
    

[站外圖片上傳中……(7)]

問題6 : 處理內存警告

   - (void)didReceiveMemoryWarning {
     [super didReceiveMemoryWarning];

     // 清除圖片緩存池
        [_imagesCache removeAllObjects];
        // 清除操作緩存池
        [_OPCache removeAllObjects];
        // 清除隊列里面所有的操作
        [_queue cancelAllOperations];

        }

問題7 : 當程序再次啟動時,內存緩存失效了;要設計沙盒緩存策略

  • 7.1在cell數據源方法中

      // 在建立下載操作之前,內存緩存判斷之后,判斷沙盒緩存
       UIImage *cacheImage = [UIImage imageWithContentsOfFile:[app.icon appendCachesPath]];
       if (cacheImage) {
            NSLog(@"從沙盒中加載...%@",app.name);
           // 在內存緩存保存一份
          [_imagesCache setObject:cacheImage forKey:app.icon];
           // 賦值
           cell.iconImageView.image = cacheImage;
          return cell;
       }
    
  • 7.2 創建了一個字符串的分類,獲取沙盒圖片緩存路徑

    - (NSString *)appendCachesPath
    {
        // 獲取沙盒路徑
      NSString  *path = 
      NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES).lastObject;
    
      // 獲取文件名 : 
      //如http://p16.qhimg.com/dr/48_48_/t0125e8d438ae9d2fbb.png
      // self : 這個方法的調用者
      // lastPathComponent : 截取網絡地址最后一個`/`后面的內容(就是圖片名)
          NSString *name = [self lastPathComponent];
    
    // 路徑拼接文件名
    // stringByAppendingPathComponent : 會自動添加`/`
        NSString *filePath = [path stringByAppendingPathComponent:name];
    
      return filePath;
      }
    

搞定
[站外圖片上傳中……(8)]

最后提供一下源碼、各個步驟都有提交

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • 1.自定義控件 a.繼承某個控件 b.重寫initWithFrame方法可以設置一些它的屬性 c.在layouts...
    圍繞的城閱讀 3,438評論 2 4
  • 有幾天沒畫了。兒子放暑假,徹底打亂了我的作息時間。 昨天韓魔在群里發了一張她臨摹的圖,大家一下子都愛上了,都說要臨...
    左巖右岸閱讀 566評論 1 2
  • 大四的時候寫的一篇日記體的文章,作于2012年的2月份。 那一年還沒有紅遍京城的雕爺牛腩薛蟠烤串,更沒有單日交易額...
    JOIN創業筆記閱讀 1,315評論 0 4
  • 公司有個注冊界面要做, 由于加了電話號碼字段,而這個字段后臺是用表單的形式寫的后臺數據;他的數據格式是applic...
    iOS之星閱讀 18,712評論 29 10