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

一、NSOperation VS GCD

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

二、最大并发数

  • 什么是并发数

同时执行的任务数

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

  • 最大并发数的相关方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
  • 执行的过程
  • 1、把操作添加到队列self.queue addOperationWithBlock
  • 2、去线程池去取空闲的线程,如果没有就创建线程
  • 3、把操作交给从线程池中取出的线程执行
  • 4、执行完成后,把线程再放回线程池中
  • 5、重复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);
}

三、操作的优先级

  • 设置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

四、操作依赖

  • NSOperation之间可以设置依赖来保证执行顺序

比如一定要让操作A执行完后,才能执行操作B,可以这么写

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

可以在不同queue的NSOperation之间创建依赖关系

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

@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];

提供方法—---生成所有对象的一个集合

  + (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的循环引用
  • 代码重构
    • 把下载图片的代码提取成方法

六、沙盒缓存

  • 先把下载的图片以文件的形式保存下来
//获取沙盒路径
- (NSString *)appendCachePath {
    return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject stringByAppendingPathComponent:self.lastPathComponent];

}
//把数据写入沙盒中
[data writeToFile:self.URLString.appendCachePath atomically:YES];

在判断完内存缓存后,如果没有,则从沙盒加载图片,虽然速度“慢点”,但是不浪费流量

沙盒

	1.	查找沙盒路径
	a.	沙盒路径   po NSHomeDirectory()
	b.	bundle路径   po [[NSBundle mainBundle] bundlePath] 
	2.	沙盒目录
	a.	Documents
保存由应用程序产生的文件或数据。例如:游戏进度、涂鸦软件的绘图
目录中的文件会自动保存到iCloud上
不要保存从网络上下来的文件
iTunes会备份
	a.	Library/Cache
保存临时文件,后续需要使用。例如:缓存图片,离线地图数据
系统不会自动清理此目录。
程序员需要提供清理此目录的功能
iTunes不会备份
	a.	Library/Preferences
用户偏好,存储用户的一些偏好操作
iTunes会备份
	a.	tmp
保存临时文件,后续不需要使用
tmp目录中的文件,系统会自动清理
系统的磁盘空间不足,会自动清理
系统重启,会清理该文件夹
iTunes不会备份

获取沙盒路径

- (instancetype)appendCachePath {
    return [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:self.lastPathComponent];
}
- (instancetype)appendDocumentPath {
    return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:self.lastPathComponent];
}
- (instancetype)appendTmpPath {
    return [NSTemporaryDirectory() stringByAppendingPathComponent:self.lastPathComponent];
}
#import "ViewController.h"
#import "HMAppInfo.h"
#import "HMAppInfoCell.h"
#import "NSString+Sandbox.h"
@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 当有些图片下载速度比较慢,上下不停滚动的时候,会重复下载图片,会浪费流量
    //判断当前是否有对应图片的下载操作,如果没有添加下载操作,如果有不要重复创建操作
    //下载操作的缓存池
//9 分析是否有循环引用的问题
    //vc -> queue 和 downloadCache ->  op  -> block  -> self(vc)
//10 如果没有联网的话,返回cell的方法会不停执行。
    //判断下载的图片是否为空
//11 沙盒缓存
@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];
    //2 获取数据,给cell内部子控件赋值
    HMAppInfo *appInfo = self.appInfos[indexPath.row];
    cell.appInfo = appInfo;
    //判断有没有图片缓存
    if (self.imageCache[appInfo.icon]) {
        NSLog(@"从缓存加载图片...");
        cell.iconView.image = self.imageCache[appInfo.icon];
        return cell;
    }
    //设置占位图片
    cell.iconView.image = [UIImage imageNamed:@"user_default"];
    //检查沙盒缓存中是否有图片
    NSData *data = [NSData dataWithContentsOfFile:[appInfo.icon appendCache]];
    if (data) {
        UIImage *img = [UIImage imageWithData:data];
        //缓存到内存
        self.imageCache[appInfo.icon] = img;
        //
        cell.iconView.image = img;
        NSLog(@"从沙盒加载图片...");
        return cell;
    }
    //异步下载图片
    [self downloadImage:indexPath];
    //3 返回cell
    return cell;
}
//异步下载图片
- (void)downloadImage:(NSIndexPath *)indexPath {
    HMAppInfo *appInfo = self.appInfos[indexPath.row];
    //判断下载操作缓存池 中是否有对应的操作
    if (self.downloadCache[appInfo.icon]) {
        NSLog(@"正在拼命下载图片...");
        return;
    }
    //异步下载图片
    //模拟网速比较慢
    //    __weak typeof(self) weakSelf = self;
    //下载操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        //        [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];
        //把图片保存到沙盒中
        if (img) {
            [data writeToFile:[appInfo.icon appendCache] atomically:YES];
        }
        //主线程上更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //如果图片不为空,才执行。如果图片为空,会不停的返回cell
            if (img) {
                //线程不安全的
                //缓存图片
                self.imageCache[appInfo.icon] = img;
                //移除下载操作缓存池
                [self.downloadCache removeObjectForKey:appInfo.icon];
                //            cell.imageView.image = img;
                //解决图片显示错行
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            }
        }];
    }];
    //把操作添加到队列中
    [self.queue addOperation:op];
    //把操作添加到下载操作缓存池中
    self.downloadCache[appInfo.icon] = op;
}
//接收到内存警告
- (void)didReceiveMemoryWarning {
    //清理内存
    [self.imageCache removeAllObjects];
    [self.downloadCache removeAllObjects];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    //点击cell的时候,输出当前队列的操作数
    NSLog(@"队列的操作数:%zd",self.queue.operationCount);
}
- (void)dealloc {
    NSLog(@"dealloc");
}

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值