美文网首页一个苹果iOS开发首页投稿(暂停使用,暂停投稿)
根据SDWebImage框架总结tableView中网络图片异步

根据SDWebImage框架总结tableView中网络图片异步

作者: 浮云我想听你说 | 来源:发表于2016-08-21 16:55 被阅读822次

    梳理一下,在开发中利用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)]

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

    相关文章

      网友评论

      本文标题:根据SDWebImage框架总结tableView中网络图片异步

      本文链接:https://www.haomeiwen.com/subject/khhwsttx.html