ios基础篇(十四)—— 操做依赖、操做缓存池

1、NSOperation VS GCD

  • GCD
    • GCD是iOS4.0 推出的,主要针对多核cpu作了优化,是C语言的技术
    • GCD是将任务(block)添加到队列(串行/并行/全局/主队列),而且以同步/异步的方式执行任务的函数
    • GCD提供了一些NSOperation不具有的功能
      • 一次性执行
      • 延迟执行
      • 调度组
  • NSOperation
    • NSOperation是iOS2.0推出的,iOS4以后重写了NSOperation
    • NSOperation将操做(异步的任务)添加到队列(并发队列),就会执行指定操做的函数
    • NSOperation里提供的方便的操做
      • 最大并发数
      • 队列的暂定/继续
      • 取消全部的操做
      • 指定操做之间的依赖关系(GCD能够用同步实现)

2、最大并发数

  • 什么是并发数

同时执行的任务数数组

好比,同时开3个线程执行3个任务,并发数就是3缓存

  • 最大并发数的相关方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
  • 执行的过程
  • 一、把操做添加到队列self.queue addOperationWithBlock
  • 二、去线程池去取空闲的线程,若是没有就建立线程
  • 三、把操做交给从线程池中取出的线程执行
  • 四、执行完成后,把线程再放回线程池中
  • 五、重复2,3,4知道全部的操做都执行完

队列的暂停、取消、恢复

取消队列的全部操做
- (void)cancelAllOperations;
提示:也能够调用NSOperation的- (void)cancel方法取消单个操做
暂停和恢复队列
- (void)setSuspended:(BOOL)b; // YES表明暂停队列,NO表明恢复队列
- (BOOL)isSuspended;

摇奖机

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *lbl1;
@property (weak, nonatomic) IBOutlet UILabel *lbl2;
@property (weak, nonatomic) IBOutlet UILabel *lbl3;
@property (weak, nonatomic) IBOutlet UIButton *startButton;
//全局队列
@property (nonatomic, strong) NSOperationQueue *queue;
@end

@implementation ViewController
//懒加载
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
//点击开始执行
- (IBAction)start:(UIButton *)sender {
    //当队列中有操做的时候,不添加操做    
    if (self.queue.operationCount == 0) {
        //异步执行 添加操做
        [self.queue addOperationWithBlock:^{
            [self random];
        }];
        [self.startButton setTitle:@"暂停" forState:UIControlStateNormal];
        self.queue.suspended = NO;
    }else if(!self.queue.isSuspended) {
        //正在执行的时候,暂停
        //先把当前的操做执行完毕,暂停后续的操做
        self.queue.suspended = YES;
        [self.startButton setTitle:@"继续" forState:UIControlStateNormal];
    }    
}
//随机生成3个数字,显示到label上
- (void)random {
    while (!self.queue.isSuspended) {
        [NSThread sleepForTimeInterval:0.05];
        //生成随机数     [0,10)  0-9
        int num1 = arc4random_uniform(10);
        int num2 = arc4random_uniform(10);
        int num3 = arc4random_uniform(10);
        //回到主线程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //给label赋值
            self.lbl1.text = [NSString stringWithFormat:@"%d",num1];
            self.lbl2.text = [NSString stringWithFormat:@"%d",num2];
            self.lbl3.text = [NSString stringWithFormat:@"%d",num3];            
        }];
    }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%zd",self.queue.operationCount);
}

3、操做的优先级

  • 设置NSOperation在queue中的优先级,能够改变操做的执行优先级
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
  • iOS8之后推荐使用服务质量 qualityOfService

监听操做完成

  • 能够监听一个操做的执行完毕
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
@interface ViewController ()
@property (nonatomic, strong) NSOperationQueue *queue;
@end

@implementation ViewController
//懒加载
- (NSOperationQueue *)queue{
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //操做1
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 20; i++) {
            NSLog(@"op1  %d",i);
        }
    }];
    //设置优先级最高
    op1.qualityOfService = NSQualityOfServiceUserInteractive;
    [self.queue addOperation:op1];    
    //等操做完成,执行  执行在子线程上
    [op1 setCompletionBlock:^{
        NSLog(@"============op1 执行完成========== %@",[NSThread currentThread]);
    }]; 
    //操做2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 20; i++) {
            NSLog(@"op2  %d",i);
        }
    }];
    //设置优先级最低
    op2.qualityOfService = NSQualityOfServiceBackground;
    [self.queue addOperation:op2];
}
@end

4、操做依赖

  • NSOperation之间能够设置依赖来保证执行顺序

好比必定要让操做A执行完后,才能执行操做B,能够这么写网络

[operationB addDependency:operationA]; // 操做B依赖于操做A

能够在不一样queue的NSOperation之间建立依赖关系并发

模拟软件升级过程:下载—解压—升级完成app

@interface ViewController ()
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation ViewController
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // 下载 - 解压 - 升级完成
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下载");
    }];    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"解压");
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"升级完成");
    }];
    //设置操做间的依赖
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    //错误,会发生循环依赖,什么都不执行
//    [op1 addDependency:op3]; 
    //操做添加到队列中
    [self.queue addOperations:@[op1,op2] waitUntilFinished:NO];
    //依赖关系能够夸队列执行
    [[NSOperationQueue mainQueue] addOperation:op3];
}

案例-UITableView中显示图片

步骤1—数据模型准备

  • 把准备好的数据源(plist)转换成咱们使用方便的对象集合

    从字典类型自定绑定属性

  [obj setValuesForKeysWithDictionary:dict];

提供方法—---生成全部对象的一个集合dom

  + (NSArray *)appList;

搭建界面

  • UITabelViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

把name和download都先显示到界面上异步

同步方式下载图片

  • 在cell生成的时候下载图片
     //模拟网络延时
    [NSThread sleepForTimeInterval:0.5];

    NSURL *url = [NSURL URLWithString:appInfo.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *img = [UIImage imageWithData:data];

问题:进入界面很慢,一滑动就卡函数

缘由:同步方式去下载图片的时候,系统没法很快执行界面渲染,俗称“卡主线程”测试

问题:每次上拉下拉的时候都会重复下载图片优化

ViewController.m

#import "ViewController.h"
#import "HMAppInfo.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
@end
//1 建立模型类,获取数据,测试
//2 数据源方法
//3 同步下载图片
@implementation ViewController
//懒加载
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 测试模型数据
//    NSLog(@"%@",self.appInfos);
}
//2 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 获取数据,给cell内部子控件赋值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];    
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //同步下载图片
    //模拟网速比较慢
    [NSThread sleepForTimeInterval:0.5];
    NSURL *url = [NSURL URLWithString:appInfo.icon];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *img = [UIImage imageWithData:data];
    cell.imageView.image = img;    
    //3 返回cell
    return cell;
}
@end

HMAppInfo.h

@interface HMAppInfo : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, copy) NSString *download;
+ (instancetype)appInfoWithDic:(NSDictionary *)dic;
//获取全部的模型数据
+ (NSArray *)appInfos;
@end

HMAppInfo.m

#import "HMAppInfo.h"
@implementation HMAppInfo
+ (instancetype)appInfoWithDic:(NSDictionary *)dic {
    HMAppInfo *appInfo = [[self alloc] init];
    //kvc给属性赋值
    [appInfo setValuesForKeysWithDictionary:dic];
    return appInfo;
}
+ (NSArray *)appInfos{
    //加载plist
    NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
    NSArray *array = [NSArray arrayWithContentsOfFile:path];    
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:10];
    //便利数组的另外一种方式
    [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        //字典转模型
        HMAppInfo *appInfo = [self appInfoWithDic:obj];
        [mArray addObject:appInfo];
    }];    
    //返回  对可变数组进行copy操做。变成不可变数组
    return mArray.copy;
}
@end

异步方式下载图片

  • 异步方式下载图片
//异步加载网络图片
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    }];
  • 问题1:图片显示不出,点击才显示
  • 缘由:cell中的imageView是懒加载的,在初始化的时候图片没有指定,因此图片大小为0x0
  • 解决:设置占位图片

ViewController.m 

#import "ViewController.h"
#import "HMAppInfo.h"
#import "HMAppInfoCell.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局队列
@property (nonatomic, strong) NSOperationQueue *queue;
@end
//1 建立模型类,获取数据,测试
//2 数据源方法
//3 同步下载图片--若是网速比较慢,界面会卡顿
//4 异步下载图片--图片显示不出来,点击cell或者上下拖动图片能够显示
    //解决,使用占位图片
@implementation ViewController
//懒加载
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 测试模型数据
//    NSLog(@"%@",self.appInfos);
}
//2 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 获取数据,给cell内部子控件赋值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];    
    //cell内部的子控件都是懒加载的
    //当返回cell以前,会调用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;
    //设置占位图片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];
    //异步下载图片
    //模拟网速比较慢
    [self.queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //主线程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            cell.imageView.image = img;
        }];
    }];
    //3 返回cell
    return cell;
}
@end

HMAppInfoCell.m 

#import "HMAppInfoCell.h"
@implementation HMAppInfoCell
- (void)layoutSubviews {
    [super layoutSubviews];    
    NSLog(@"layoutSubviews");
}
@end
  • 问题2:频繁滚动时,反复下载相同图片
  • 解决:图片缓存,模型类中增长image的属性

 

  • 问题3:频繁滚动时,而且超出屏幕的图片下载速度比较慢的状况,图片可能错位,而且图片来回跳
  • 缘由:cell的重用
  • 解决:当异步下载完成后,回到主线程从新加载对应的cell

ViewController.m

#import "ViewController.h"
#import "HMAppInfo.h"
#import "HMAppInfoCell.h"
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局队列
@property (nonatomic, strong) NSOperationQueue *queue;
@end
//1 建立模型类,获取数据,测试
//2 数据源方法
//3 同步下载图片--若是网速比较慢,界面会卡顿
//4 异步下载图片--图片显示不出来,点击cell或者上下拖动图片能够显示
    //解决,使用占位图片
//5 图片缓存--把网络上下载的图片,保存到内存
    //解决,图片重复下载,把图片缓存到内存中,节省用户的流量 (拿空间换取执行时间)
//6 解决图片下载速度特别慢,出现的错行问题。
    //当图片下载完成以后,从新加载对应的cell
@implementation ViewController
//懒加载
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 测试模型数据
//    NSLog(@"%@",self.appInfos);
}
//2 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 获取数据,给cell内部子控件赋值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];   
    //cell内部的子控件都是懒加载的
    //当返回cell以前,会调用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //判断有没有图片缓存
    if (appInfo.image) {
        NSLog(@"从缓存加载图片...");
        cell.imageView.image = appInfo.image;
        return cell;
    } 
    //设置占位图片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];    
    //异步下载图片
    //模拟网速比较慢
    [self.queue addOperationWithBlock:^{
//        [NSThread sleepForTimeInterval:0.5];
        //模拟图片下载速度慢
        if (indexPath.row > 9) {
            [NSThread sleepForTimeInterval:5];
        }        
        //下载图片
        NSLog(@"下载网络图片...");
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //缓存图片
        appInfo.image = img;        
        //主线程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
//            cell.imageView.image = img;
            //解决图片显示错行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];
    //3 返回cell
    return cell;
}
@end

操做缓存池

  • 问题:因为滑动过快致使屡次下载相同图片
  • 解决:操做缓存
  • 定义一个字典,把每次操做都放在里面,避免相同操做的出现 
  NSMutableDictionary *operationCaches;
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局队列
@property (nonatomic, strong) NSOperationQueue *queue;
//图片的缓存池
@property (nonatomic, strong) NSMutableDictionary *imageCache;
//下载操做缓存池
@property (nonatomic, strong) NSMutableDictionary *downloadCache;
@end
//1 建立模型类,获取数据,测试
//2 数据源方法
//3 同步下载图片--若是网速比较慢,界面会卡顿
//4 异步下载图片--图片显示不出来,点击cell或者上下拖动图片能够显示
    //解决,使用占位图片
//5 图片缓存--把网络上下载的图片,保存到内存--图片存储在模型对象中
    //解决,图片重复下载,把图片缓存到内存中,节省用户的流量 (拿空间换取执行时间)
//6 解决图片下载速度特别慢,出现的错行问题。
    //当图片下载完成以后,从新加载对应的cell
//7 当收到内存警告,要清理内存,若是图片存储在模型对象中,很差清理内存
    //图片的缓存池
//8 当有些图片下载速度比较慢,上下不停滚动的时候,会重复下载图片,会浪费流量
    //判断当前是否有对应图片的下载操做,若是没有添加下载操做,若是有不要重复建立操做
    //下载操做的缓存池
@implementation ViewController
//懒加载
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (NSMutableDictionary *)imageCache {
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _imageCache;
}
- (NSMutableDictionary *)downloadCache {
    if (_downloadCache == nil) {
        _downloadCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _downloadCache;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //1 测试模型数据
//    NSLog(@"%@",self.appInfos);
}
//2 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 获取数据,给cell内部子控件赋值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];
    
    //cell内部的子控件都是懒加载的
    //当返回cell以前,会调用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //判断有没有图片缓存
    if (self.imageCache[appInfo.icon]) {
        NSLog(@"从缓存加载图片...");
        cell.imageView.image = self.imageCache[appInfo.icon];
        return cell;
    }    
    //设置占位图片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];    
    //判断下载操做缓存池 中是否有对应的操做
    if (self.downloadCache[appInfo.icon]) {
        NSLog(@"正在拼命下载图片...");
        return cell;
    }
    //异步下载图片
    //模拟网速比较慢    
    //下载操做
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        //        [NSThread sleepForTimeInterval:0.5];
        //模拟图片下载速度慢
        if (indexPath.row > 9) {
            [NSThread sleepForTimeInterval:10];
        }        
        //下载图片
        NSLog(@"下载网络图片...");        
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //缓存图片
        self.imageCache[appInfo.icon] = img;        
        //主线程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //            cell.imageView.image = img;
            //解决图片显示错行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];    
    //把操做添加到队列中
    [self.queue addOperation:op];
    //把操做添加到下载操做缓存池中
    self.downloadCache[appInfo.icon] = op;    
    //3 返回cell
    return cell;
}
//接收到内存警告
- (void)didReceiveMemoryWarning {
    //清理内存
    [self.imageCache removeAllObjects];
    //
    [self.downloadCache removeAllObjects];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //点击cell的时候,输出当前队列的操做数
    NSLog(@"队列的操做数:%zd",self.queue.operationCount);
    
}
@end
@interface ViewController ()
@property (nonatomic, strong) NSArray *appInfos;
//全局队列
@property (nonatomic, strong) NSOperationQueue *queue;
//图片的缓存池
@property (nonatomic, strong) NSMutableDictionary *imageCache;
//下载操做缓存池
@property (nonatomic, strong) NSMutableDictionary *downloadCache;
@end
//1 建立模型类,获取数据,测试
//2 数据源方法
//3 同步下载图片--若是网速比较慢,界面会卡顿
//4 异步下载图片--图片显示不出来,点击cell或者上下拖动图片能够显示
    //解决,使用占位图片
//5 图片缓存--把网络上下载的图片,保存到内存--图片存储在模型对象中
    //解决,图片重复下载,把图片缓存到内存中,节省用户的流量 (拿空间换取执行时间)
//6 解决图片下载速度特别慢,出现的错行问题。
    //当图片下载完成以后,从新加载对应的cell
//7 当收到内存警告,要清理内存,若是图片存储在模型对象中,很差清理内存
    //图片的缓存池
//8 当有些图片下载速度比较慢,上下不停滚动的时候,会重复下载图片,会浪费流量
    //判断当前是否有对应图片的下载操做,若是没有添加下载操做,若是有不要重复建立操做
    //下载操做的缓存池
@implementation ViewController
//懒加载
- (NSArray *)appInfos {
    if (_appInfos == nil) {
        _appInfos = [HMAppInfo appInfos];
    }
    return _appInfos;
}
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [NSOperationQueue new];
    }
    return _queue;
}
- (NSMutableDictionary *)imageCache {
    if (_imageCache == nil) {
        _imageCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _imageCache;
}
- (NSMutableDictionary *)downloadCache {
    if (_downloadCache == nil) {
        _downloadCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _downloadCache;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //1 测试模型数据
//    NSLog(@"%@",self.appInfos);
}
//2 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.appInfos.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //1 建立可重用的cell
    static NSString *reuseId = @"appInfo";
    HMAppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId];
    if (cell == nil) {
        cell = [[HMAppInfoCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId];
    }
    //2 获取数据,给cell内部子控件赋值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];    
    //cell内部的子控件都是懒加载的
    //当返回cell以前,会调用cell的layoutSubviews方法
    cell.textLabel.text = appInfo.name;
    cell.detailTextLabel.text = appInfo.download;    
    //判断有没有图片缓存
    if (self.imageCache[appInfo.icon]) {
        NSLog(@"从缓存加载图片...");
        cell.imageView.image = self.imageCache[appInfo.icon];
        return cell;
    }    
    //设置占位图片
    cell.imageView.image = [UIImage imageNamed:@"user_default"];    
    //判断下载操做缓存池 中是否有对应的操做
    if (self.downloadCache[appInfo.icon]) {
        NSLog(@"正在拼命下载图片...");
        return cell;
    }
    //异步下载图片
    //模拟网速比较慢    
    //下载操做
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        //        [NSThread sleepForTimeInterval:0.5];
        //模拟图片下载速度慢
        if (indexPath.row > 9) {
            [NSThread sleepForTimeInterval:10];
        }        
        //下载图片
        NSLog(@"下载网络图片...");        
        NSURL *url = [NSURL URLWithString:appInfo.icon];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //缓存图片
        self.imageCache[appInfo.icon] = img;        
        //主线程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //            cell.imageView.image = img;
            //解决图片显示错行
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        }];
    }];    
    //把操做添加到队列中
    [self.queue addOperation:op];
    //把操做添加到下载操做缓存池中
    self.downloadCache[appInfo.icon] = op;    
    //3 返回cell
    return cell;
}
//接收到内存警告
- (void)didReceiveMemoryWarning {
    //清理内存
    [self.imageCache removeAllObjects];
    [self.downloadCache removeAllObjects];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //点击cell的时候,输出当前队列的操做数
    NSLog(@"队列的操做数:%zd",self.queue.operationCount);
}
@end

注意

  • Block的循环引用
  • 代码重构
    • 把下载图片的代码提取成方法
相关文章
相关标签/搜索