根据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阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 1.自定义控件 a.继承某个控件 b.重写initWithFrame方法可以设置一些它的属性 c.在layouts...
    圍繞的城阅读 3,345评论 2 4
  • 有几天没画了。儿子放暑假,彻底打乱了我的作息时间。 昨天韩魔在群里发了一张她临摹的图,大家一下子都爱上了,都说要临...
    左岩右岸阅读 553评论 1 2
  • 大四的时候写的一篇日记体的文章,作于2012年的2月份。 那一年还没有红遍京城的雕爷牛腩薛蟠烤串,更没有单日交易额...
    JOIN创业笔记阅读 1,267评论 0 4
  • 公司有个注册界面要做, 由于加了电话号码字段,而这个字段后台是用表单的形式写的后台数据;他的数据格式是applic...
    iOS之星阅读 18,669评论 29 10