根据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错行

解决办法 : 刷新对应的行

 // 图片异步下载完成之后,刷新对应的行,并且不要动画(偷偷的,不让用户发现)
 [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)]

最后提供一下源码、各个步骤都有提交

你可能感兴趣的:(根据SDWebImage框架总结tableView中网络图片异步下载可能会遇到的问题)