根據(jù)SDWebImage框架總結(jié)tableView中網(wǎng)絡(luò)圖片異步下載可能會(huì)遇到的問(wèn)題

梳理一下,在開(kāi)發(fā)中利用SDWebImage下載圖片 蒲每,這個(gè)框架會(huì)幫我們做什么事情纷跛。

這里自己寫(xiě)代碼來(lái)實(shí)現(xiàn)解決所有的問(wèn)題。

項(xiàng)目準(zhǔn)備:

  • 1.首先創(chuàng)建數(shù)據(jù)源數(shù)組

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

      ///定義獲取JSON的主方法
      -(void)loadJsonData{
      
      //1邀杏、創(chuàng)建網(wǎng)絡(luò)請(qǐng)求管理者
      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) {
      //請(qǐng)求網(wǎng)絡(luò)執(zhí)行 回調(diào)(成功/失敗)都是在主線程
      NSLog(@"%@   %@ \n %@",[responseObject class],responseObject,[NSThread currentThread]);
      
      //responseObject就是獲取到的json數(shù)據(jù)
      //1望蜡、遍歷數(shù)據(jù)數(shù)組字典轉(zhuǎn)模型
      //4唤崭、創(chuàng)建可變數(shù)組用來(lái)保存模型
      NSMutableArray *modelsArr = [NSMutableArray arrayWithCapacity:responseObject.count];
      
      [responseObject enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
             //2、創(chuàng)建模型類(lèi)
          //3脖律、賦值
          FYAppModel *model = [FYAppModel appWithDict:obj];
          
          [modelsArr addObject:model];
      }];
      _appsArrM = modelsArr.copy;
      //網(wǎng)絡(luò)請(qǐng)求是耗時(shí)操作谢肾,拿到數(shù)據(jù)一定要reloadData
      [self.tableView reloadData];
    
       } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
      
       }];
      }
    
  • 3.實(shí)現(xiàn)tableView的數(shù)據(jù)源方法

      #pragma mark - 數(shù)據(jù)源方法
      -(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;
      }
    

項(xiàng)目準(zhǔn)備完畢


接下來(lái)的實(shí)現(xiàn)不再用SDWebImage,自己實(shí)現(xiàn)NSBlockOperation異步下載圖片小泉,看看我們遇到了什么問(wèn)題芦疏,也就是他幫助我們做了什么。

增加全局隊(duì)列
     @implementation ViewController {

        /// 數(shù)據(jù)源數(shù)組
        NSArray *_appsList;
        /// 全局隊(duì)列
        NSOperationQueue *_queue;
        
    }

實(shí)例化隊(duì)列

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

問(wèn)題1 : 列表顯示出來(lái)后微姊,并不顯示圖片酸茴,來(lái)回滾動(dòng)cell或者點(diǎn)擊cell ,圖片才會(huì)顯示兢交。

不顯示圖片
不顯示圖片

解決辦法 : 自定義cell

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

    // 獲取cell對(duì)應(yīng)的數(shù)據(jù)模型
     AppsModel *app = _appsList[indexPath.row];

    // 給cell傳入模型對(duì)象
     cell.app = app;

    #**pragma mark - NSBlockOperation實(shí)現(xiàn)圖片的異步下載**
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    
    // 模擬網(wǎng)絡(luò)延遲
    [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;
    }];
}];

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

        return cell;
    }

原理:

1.cell上的系統(tǒng)默認(rèn)的子控件都是懶加載上去的

2.在返回cell之前,如果沒(méi)有給cell上的默認(rèn)的子控件賦值,那么這個(gè)默認(rèn)的子控件就不會(huì)加載到cell上;

3.跟cell做交互(點(diǎn)擊)時(shí),默認(rèn)會(huì)自動(dòng)調(diào)用layoutSubViews方法,重新布局了子控件薪捍。

問(wèn)題2 : 當(dāng)有網(wǎng)絡(luò)延遲時(shí),來(lái)回滾動(dòng)cell,會(huì)出現(xiàn)cell上圖片的閃動(dòng);因?yàn)閏ell有復(fù)用

圖片的閃動(dòng)
圖片的閃動(dòng)

解決辦法 : 占位圖

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

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

解決辦法 : 設(shè)計(jì)內(nèi)存緩存策略 (字典)

  • 3.1增加圖片緩存池

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

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

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

      NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"從網(wǎng)絡(luò)中加載...%@",app.name);
      // 模擬網(wǎng)絡(luò)延遲
      [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];
                }
           }];
      }];
    
建立圖片緩存,避免重復(fù)下載
建立圖片緩存晴裹,避免重復(fù)下載

問(wèn)題4 : 當(dāng)有網(wǎng)絡(luò)延遲時(shí),滾動(dòng)cell會(huì)出現(xiàn)圖片錯(cuò)行的問(wèn)題

cell錯(cuò)行
cell錯(cuò)行

解決辦法 : 刷新對(duì)應(yīng)的行

 // 圖片異步下載完成之后,刷新對(duì)應(yīng)的行被济,并且不要?jiǎng)赢?huà)(偷偷的,不讓用戶發(fā)現(xiàn))
 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

問(wèn)題5 : 當(dāng)有網(wǎng)絡(luò)延遲時(shí),來(lái)回滾動(dòng)cell,會(huì)重復(fù)建立下載操作

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

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

  • 5.1建立操作緩存池

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

     _OPCache = [[NSMutableDictionary alloc] init];
    
  • 5.3模擬網(wǎng)絡(luò)延遲

     // 在建立下載操作之前,判斷下載操作是否存在
      if ([_OPCache objectForKey:app.icon] != nil) {
           NSLog(@"正在下載中...%@",app.name);
           return cell;
       }
    
     NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
      ***************************************
     // 模擬網(wǎng)絡(luò)延遲 : 讓屏幕之外的圖片的下載延遲時(shí)間比較長(zhǎng)
     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];
      ***************************************
      // 把操作添加到隊(duì)列
      [_queue addOperation:op];
    
     return cell;
     }
    

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

問(wèn)題6 : 處理內(nèi)存警告

   - (void)didReceiveMemoryWarning {
     [super didReceiveMemoryWarning];

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

        }

問(wèn)題7 : 當(dāng)程序再次啟動(dòng)時(shí),內(nèi)存緩存失效了;要設(shè)計(jì)沙盒緩存策略

  • 7.1在cell數(shù)據(jù)源方法中

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末净响,一起剝皮案震驚了整個(gè)濱河市少欺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌馋贤,老刑警劉巖赞别,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異配乓,居然都是意外死亡仿滔,警方通過(guò)查閱死者的電腦和手機(jī)惠毁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)崎页,“玉大人鞠绰,你說(shuō)我怎么就攤上這事§梗” “怎么了蜈膨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)牺荠。 經(jīng)常有香客問(wèn)我翁巍,道長(zhǎng),這世上最難降的妖魔是什么休雌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任灶壶,我火速辦了婚禮,結(jié)果婚禮上杈曲,老公的妹妹穿的比我還像新娘驰凛。我一直安慰自己,他們只是感情好鱼蝉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布议薪。 她就那樣靜靜地躺著迄汛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上张峰,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音串慰,去河邊找鬼栋盹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛利术,可吹牛的內(nèi)容都是我干的呈野。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼印叁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼被冒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起轮蜕,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤昨悼,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后跃洛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體率触,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年汇竭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葱蝗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穴张。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖两曼,靈堂內(nèi)的尸體忽然破棺而出皂甘,到底是詐尸還是另有隱情,我是刑警寧澤合愈,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布叮贩,位于F島的核電站,受9級(jí)特大地震影響佛析,放射性物質(zhì)發(fā)生泄漏益老。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一寸莫、第九天 我趴在偏房一處隱蔽的房頂上張望捺萌。 院中可真熱鬧,春花似錦膘茎、人聲如沸桃纯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)态坦。三九已至,卻和暖如春棒拂,著一層夾襖步出監(jiān)牢的瞬間伞梯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工帚屉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谜诫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓攻旦,卻偏偏與公主長(zhǎng)得像喻旷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子牢屋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1.自定義控件 a.繼承某個(gè)控件 b.重寫(xiě)initWithFrame方法可以設(shè)置一些它的屬性 c.在layouts...
    圍繞的城閱讀 3,389評(píng)論 2 4
  • 有幾天沒(méi)畫(huà)了且预。兒子放暑假,徹底打亂了我的作息時(shí)間烙无。 昨天韓魔在群里發(fā)了一張她臨摹的圖锋谐,大家一下子都愛(ài)上了,都說(shuō)要臨...
    左巖右岸閱讀 555評(píng)論 1 2
  • 大四的時(shí)候?qū)懙囊黄沼涹w的文章皱炉,作于2012年的2月份怀估。 那一年還沒(méi)有紅遍京城的雕爺牛腩薛蟠烤串狮鸭,更沒(méi)有單日交易額...
    JOIN創(chuàng)業(yè)筆記閱讀 1,293評(píng)論 0 4
  • 公司有個(gè)注冊(cè)界面要做, 由于加了電話號(hào)碼字段,而這個(gè)字段后臺(tái)是用表單的形式寫(xiě)的后臺(tái)數(shù)據(jù);他的數(shù)據(jù)格式是applic...
    iOS之星閱讀 18,685評(píng)論 29 10