一、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");
}