利用NSOperation进行异步图片下载——设置UITabView数据,图片下载,占位图。解决异步下载和Cell重用造成的图片设置混乱。和多次重复下载的问题...

同步下载图片:同步下载图片。就是直接下载,然后设置。因为下载是一个耗时操作,同步执行又限制当前操作执行不完就不允许执行下一个操作。从而造成界面卡顿的现象。如下图所示:


为了解决这个问题:需要使用异步下载:即当前操作不执行完毕可以执行下一个操作。如下图所示:


占位图:UITabViewCell中UIImageView是懒加载的方式执行的,如果没有图片是默认不显示的。所以一加载完毕是没有图片显示的图片的frame为0 。点击Cell会触发Cell的layoutSubView操作,才会给Cell重新布局。layoutSubView是UIView的方法,根据组件的大小进行设置,如果是图片则根据图片的实际大小进行布局。这时在图片下载完成之前就要使用占位图。


异步下载:异步下载图片不可以直接给Cell设置图片,因为Cell是重用的,重用后会重新赋值造成图片数据混乱。最好的解决方法是利用MVC为Model设置下载后的数据进行保存。也就是说给Cell赋值不可以跳过Model直接赋值。






利用MVC解决方案如下:


利用MVC通过Model为Cell设置数据,避免了直接为Cell赋值由于Cell的重用造成的数据混乱的问题。

另外加了一个判断,如果Model有数据直接为Cell赋值,没有数据的情况下才进行异步任务下载图片,下载完成后刷新当前Cell。从而解决了重用多次重复下载的问题。

特别注意:刷新Cell,意味着重新初始化Cell。重新从头开始运行,再执行一次判断,Model是否有图片数据,如果有的话就直接设置。

新建工程,操作步骤如下:


2


3


4


5


6


代码如下:

新建Model如下:

CZApp.h

//
//  CZApp.h
//  NSoperation之网络图片下载
//
//  Created by apple on 15/10/23.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface CZApp : NSObject
@property(nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, strong) NSString *download;

/**
 保存网络下载的图像
 */
@property(nonatomic, strong) UIImage *image;

+(instancetype) appWithDict:(NSDictionary *) dict;
@end
CZApp.m

//
//  CZApp.m
//  NSoperation之网络图片下载
//
//  Created by apple on 15/10/23.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import "CZApp.h"

@implementation CZApp
+(instancetype) appWithDict:(NSDictionary *) dict
{
    CZApp *app = [[self alloc] init];
    [app setValuesForKeysWithDictionary:dict];
    return app;
}
@end

控制器代码如下:

//
//  ViewController.m
//  NSoperation之网络图片下载
//
//  Created by apple on 15/10/23.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import "ViewController.h"
#import "CZApp.h"
@interface ViewController ()
// plist文件数据的容器
@property (nonatomic, strong) NSArray *appList;

// 管理下载的全局队列
@property (nonatomic, strong) NSOperationQueue *opQueue;

@end

@implementation ViewController

// 懒加载
-(NSArray *)appList
{
    if (_appList == nil) {
        NSArray *dicArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        // 字典转模型
        NSMutableArray *arryM = [NSMutableArray array];
        for(NSDictionary *dict in dicArray){
            CZApp *app = [CZApp appWithDict:dict];
            [arryM addObject:app];
        }
        _appList = arryM;
    }
    return _appList;
}

-(NSOperationQueue *)opQueue
{
    if (_opQueue == nil) {
        _opQueue = [[NSOperationQueue alloc] init];
    }
    return _opQueue;
}

#pragma mark - 实现数据源方法
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.appList.count;
}

/**
 问题1:如果网速比较慢,会很卡
 解决方法:使用异步下载
 
 问题2:图片没有Frame,所有cell初始化的时候,给imageView的frame是0。异步下载完之后不显示
 解决办法:使用占位图(如果展位图比较大, 自定义cell可以解决)
 
 问题3:单元格重用,下拉后造成图片设置错乱
 */
// cell里面的imageView子控件是懒加载
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"AppCell";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
    // 给Cell设置数据
    CZApp *app = self.appList[indexPath.row];
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;
    
    // 判断模型里面是否有图像
    if (app.image) {
        NSLog(@" 图片尚未下载......");
        cell.imageView.image = app.image;
    }else{ // 如果模型内没有图片就异步下载
        
        // 显示图片
        cell.imageView.image = [UIImage imageNamed:@"user_default"];
        
        // 异步下载图片
        NSBlockOperation  *downLoadOp = [NSBlockOperation blockOperationWithBlock:^{
            // 模拟延时
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"正在下载中......");
            
            //  1. 下载图片(二进制数据)
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
            
            //  2. 将下载的数据保存到模型
            app.image = [UIImage imageWithData:data];
            
            //  3. 在主线程更新UI
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                //            cell.imageView.image = [UIImage imageWithData:data];
                // 因为Cell的图片是懒加载的方式添加的。只设置了Image但是没有设置Frame,只有调用layoutSubView()方法才会给Cell子控件重新布局
                // layoutSubView是UIView的方法
                // 注意:点击Cell也会触发layoutSubView()方法
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                /** reload 会重新调用cell的初始化方法, 会重新判断模型里面是否有图像
                 有的话就直接显示
                 */
            }];
        }];
        
        // 将操作添加到队列
        [self.opQueue addOperation:downLoadOp];
        
    }
    return cell;
}
@end




控制器代码重构后如下所示:

//
//  ViewController.m
//  NSoperation之网络图片下载
//
//  Created by apple on 15/10/23.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//

#import "ViewController.h"
#import "CZApp.h"
@interface ViewController ()
// plist文件数据的容器
@property (nonatomic, strong) NSArray *appList;

// 管理下载的全局队列
@property (nonatomic, strong) NSOperationQueue *opQueue;

/**所有下载的缓冲池*/
@property (nonatomic, strong) NSMutableDictionary *operationCache;
@end

@implementation ViewController

// 懒加载
-(NSArray *)appList
{
    if (_appList == nil) {
        NSArray *dicArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        // 字典转模型
        NSMutableArray *arryM = [NSMutableArray array];
        for(NSDictionary *dict in dicArray){
            CZApp *app = [CZApp appWithDict:dict];
            [arryM addObject:app];
        }
        _appList = arryM;
    }
    return _appList;
}

-(NSOperationQueue *)opQueue
{
    if (_opQueue == nil) {
        _opQueue = [[NSOperationQueue alloc] init];
    }
    return _opQueue;
}

-(NSMutableDictionary *)operationCache
{
    if (_operationCache == nil) {
        _operationCache = [[NSMutableDictionary alloc] init];
    }
    return _operationCache;
}
#pragma mark - 实现数据源方法
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.appList.count;
}

/**
 问题1:如果网速比较慢,会很卡
 解决方法:使用异步下载
 
 问题2:图片没有Frame,所有cell初始化的时候,给imageView的frame是0。异步下载完之后不显示  解决办法:使用占位图(如果展位图比较大, 自定义cell可以解决)
 
 问题3:如果图片下载速度不一致,同时用户快速滚动的时候,会因为Cell的重用导致图片混乱
 解决方法:MVC,使用Model(模型)保存下载的图像,再次刷新表格。
 
 问题4:在用户快速滚动的时候,会重复添加下载任务到下载队列。
 解决方法:建立下载操作的缓冲池。首先检查缓冲池里是否有当前图片的下载操作。有的话就不创建下载操作。从而保证一张图片只添加一个下载操作。其实就是建立一个全局的字典属性。
 */

/**
 代码重构:1.如果代码太长。
 目的:
 - 写的时候,如果思路清楚,能够一次性写完,但是也要注意同构。
 - 时间长了,不好阅读
 - 重构代码,便于维护
 
 重构方法:
如果有一部分代码专门解决某一问题,就封装起来。
 1. 新建一个方法—> 剪切代码。
 2. 传参数。
 3. 在原来剪切代码的地方,调用抽取的方法。
 4. 注意,测试。
 5. 注意if嵌套,在实际的开发,非常忌讳很深的嵌套。
 */
// cell里面的imageView子控件是懒加载
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"AppCell";
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
    // 给Cell设置数据
    CZApp *app = self.appList[indexPath.row];
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;
    
    // 判断模型里面是否有图像
    if (app.image) {
        NSLog(@" 图片已经下载......");
        cell.imageView.image = app.image;
    }else{ // 如果模型内没有图片就异步下载
        
        // 显示图片—占位图
        cell.imageView.image = [UIImage imageNamed:@"user_default"];
     #warning 从这里开始剪切的代码
        // 下载图片
        [self downloadImage:indexPath];
    }
    return cell;
}

-(void)downloadImage:(NSIndexPath *)indexPath
{
    CZApp *app = self.appList[indexPath.row];
    /**
     如果下载缓冲池里面有当前图片的下载操作,就不用创建下载操作,没有才创建
     缓冲池字典中 key:存放当前图片的url,字符串类型。
     Value:保存下载操作
     */
    if (self.operationCache[app.icon]) {
        NSLog(@"正在玩命下载中......");
        return;
    }
        // 缓冲池没有下载操作
        
        // 异步下载图片
        NSBlockOperation  *downLoadOp = [NSBlockOperation blockOperationWithBlock:^{
            // 模拟延时
            [NSThread sleepForTimeInterval:2];
            NSLog(@"正在下载中......");
            
            //  1. 下载图片(二进制数据)
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
            
            //  2. 将下载的数据保存到模型
            app.image = [UIImage imageWithData:data];
            
            //  3. 在主线程更新UI
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                //            cell.imageView.image = [UIImage imageWithData:data];
                // 因为Cell的图片是懒加载的方式添加的。只设置了Image但是没有设置Frame,只有调用layoutSubView()方法才会给Cell子控件重新布局
                // layoutSubView是UIView的方法
                // 注意:点击Cell也会触发layoutSubView()方法
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
                /** reload 会重新调用cell的初始化方法, 会重新判断模型里面是否有图像
                 有的话就直接显示
                 */
            }];
        }];
        
        // 将操作添加到队列
        [self.opQueue addOperation:downLoadOp];
        NSLog(@"操作的数量------------->%ld", self.opQueue.operationCount);
        
        // 将操作添加到缓冲池中(使用图片的url作为key)
        [self.operationCache setObject:downLoadOp forKey:app.icon];
}
@end


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值