一、NSOperation特性
1、灵活
2、自如
二、如何使用NSOperation
1、使用NSOperation分三步:创建操作、创建队列、把操作加入队列
NSOperation 是抽象类,我们只能使用其子类NSInvocationOperation和NSBlockOperation
1.1、NSInvocationOperation
- (void)demo
{
// 处理事务
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"hehe"];
// 创建队列 --- 最重要的核心点
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 操作加入队列
[queue addOperation:op];
}
-----打印:
hehe --- <NSThread: 0x60000393a500>{number = 7, name = (null)}
- (void)demo1_1
{
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
[op start];
}
------打印:
123 --- <NSThread: 0x600003c90cc0>{number = 1, name = main}
- (void)demo1_2
{
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op];
[op start];
}
-----打印:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSInvocationOperation start]: something is trying to start the receiver simultaneously from more than one thread'
-----原因:
任务running中再次将任务start会造成混乱,所以最好不要直接start
- (void)demo1_3
{
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
[[NSOperationQueue mainQueue] addOperation:op];
}
-----打印:
123 --- <NSThread: 0x6000027c4cc0>{number = 1, name = main}
1.2、NSBlockOperation
- (void)demo2
{
// 函数式编程block比target的好处:
// 1、两种不同的timer实现比较 :block更精确,target有可能会收到runloop影响)
// 2、代码可读性强 ---因为--- 业务逻辑和功能逻辑不分离
//1:创建blockOperation -- 函数式编程
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
sleep(3);
}];
//1.1 添加执行代码块
[bo addExecutionBlock:^{
NSLog(@"这是一个执行代码块 - %@",[NSThread currentThread]);
}];
//1.2 设置监听
bo.completionBlock = ^{
NSLog(@"完成了!!!");
};
//2:创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//3:添加到队列
[queue addOperation:bo];
NSLog(@"事务添加进了NSOperationQueue");
}
-----打印:
事务添加进了NSOperationQueue
<NSThread: 0x600000b4d580>{number = 4, name = (null)}
这是一个执行代码块 - <NSThread: 0x600000b4e180>{number = 5, name = (null)}
完成了!!!
-----原因:
NSOperation依赖队列执行,具有异步性,所以先执行了主线程的nslog
-----思考:
为什么要 addExecutionBlock-添加任务
-----原因:
比gcd方便的地方----可控性。
gcd不能控制(例:取消)这个任务,但是NSOperation可以。
/**
测试操作与队列的执行效果:异步并发
*/
- (void)demo3
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i<20; i++)
{
[queue addOperationWithBlock:^{
NSLog(@"%@---%d",[NSThread currentThread],i);
}];
}
}
-----打印:
<NSThread: 0x600001444d80>{number = 6, name = (null)}---0
<NSThread: 0x600001446600>{number = 4, name = (null)}---1
<NSThread: 0x60000147e800>{number = 7, name = (null)}---2
<NSThread: 0x600001446580>{number = 3, name = (null)}---3
<NSThread: 0x600001446600>{number = 4, name = (null)}---4
<NSThread: 0x600001444d80>{number = 6, name = (null)}---5
<NSThread: 0x600001446580>{number = 3, name = (null)}---6
<NSThread: 0x600001446600>{number = 4, name = (null)}---7
<NSThread: 0x600001444d80>{number = 6, name = (null)}---8
<NSThread: 0x60000147e800>{number = 7, name = (null)}---9
<NSThread: 0x600001446580>{number = 3, name = (null)}---10
<NSThread: 0x600001446600>{number = 4, name = (null)}---11
<NSThread: 0x60000147a780>{number = 8, name = (null)}---12 // 注意这里
<NSThread: 0x60000147f740>{number = 10, name = (null)}---14 // 注意这里
<NSThread: 0x600001477640>{number = 9, name = (null)}---13 // 注意这里
<NSThread: 0x600001444d80>{number = 6, name = (null)}---15
<NSThread: 0x600001446580>{number = 3, name = (null)}---16
<NSThread: 0x60000147a780>{number = 8, name = (null)}---17
<NSThread: 0x60000147a940>{number = 11, name = (null)}---18
<NSThread: 0x600001477440>{number = 12, name = (null)}---19
/**
优先级,只会让CPU有更高的几率调用,不是说设置高就一定全部先完成
*/
- (void)demo4
{
// 优先级
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"**第一个操作** %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置优先级 - 最高
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
//创建第二个操作
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++)
{
NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置优先级 - 最低
bo2.qualityOfService = NSQualityOfServiceBackground;
//2:创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//3:添加到队列 -- 先添加bo2再添加bo1
[queue addOperation:bo2];
[queue addOperation:bo1];
}
-----打印:
**第一个操作** 0 --- <NSThread: 0x600003ec0940>{number = 4, name = (null)}
**第一个操作** 1 --- <NSThread: 0x600003ec0940>{number = 4, name = (null)}
**第一个操作** 2 --- <NSThread: 0x600003ec0940>{number = 4, name = (null)}
**第一个操作** 3 --- <NSThread: 0x600003ec0940>{number = 4, name = (null)}
**第一个操作** 4 --- <NSThread: 0x600003ec0940>{number = 4, name = (null)}
第二个操作 0 --- <NSThread: 0x600003ee8e00>{number = 7, name = (null)}
第二个操作 1 --- <NSThread: 0x600003ee8e00>{number = 7, name = (null)}
第二个操作 2 --- <NSThread: 0x600003ee8e00>{number = 7, name = (null)}
第二个操作 3 --- <NSThread: 0x600003ee8e00>{number = 7, name = (null)}
第二个操作 4 --- <NSThread: 0x600003ee8e00>{number = 7, name = (null)}
/**
线程通讯
*/
- (void)demo5
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"hehe";
[queue addOperationWithBlock:^{
NSLog(@"%@ = %@",[NSOperationQueue currentQueue],[NSThread currentThread]);
//模拟请求网络
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%@ --%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
}];
}];
}
-----打印:
<NSOperationQueue: 0x7ffd78f05400>{name = 'hehe'} = <NSThread: 0x60000305bc00>{number = 5, name = (null)}
<NSOperationQueue: 0x7ffd78e01da0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003014a80>{number = 1, name = main}
- (void)demo6
{
// 设置并发数
// GCD 通过 设置信号量 来 控制并发数
self.queue.name = @"hehe";
self.queue.maxConcurrentOperationCount = 2;
// self.queue + op
for (int i = 0; i<10; i++)
{
[self.queue addOperationWithBlock:^{ // 一个任务
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
-----打印:
这个没法演示,运行的情况就是每隔两秒打印两个nslog
原因:每次执行两个任务,两个同时等了两秒后都执行完了,再执行后两个
2、设置依赖关系 --- 最重要
- (void)demo2
{
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"请求token");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着token,请求数据1");
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着数据1,请求数据2");
}];
// 建立依赖 -- 循环乔涛
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
// [bo1 addDependency:bo3];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"执行完了?我要干其他事");
}
-----打印:
请求token
拿着token,请求数据1
拿着数据1,请求数据2
执行完了?我要干其他事
3、关于operationQueue的挂起,继续,取消
- (void)demo1
{
self.queue.name = @"hehe";
self.queue.maxConcurrentOperationCount = 2;
for (int i=0; i< 100; i++)
{
[self.queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@-----%d",[NSThread currentThread],i);
}];
}
}
- (IBAction)pauseOrContinue:(id)sender
{
if (self.queue.operationCount == 0) {
NSLog(@"当前没有操作,没有必要挂起和继续");
return;
}
// 正在执行的操作无法挂起
if (self.queue.suspended)
{
NSLog(@"当前是挂起状态,准备继续");
}
else
{
NSLog(@"当前为执行状态,准备挂起");
}
self.queue.suspended = !self.queue.isSuspended;
}
- (IBAction)cancel:(id)sender
{
// 执行结果发现,正在执行的操作无法取消,因为这要回想到之前的NSThread
// 取消操作之后,再点继续,发现没有调度的任务了
[self.queue cancelAllOperations]; // 将队列queue剩余任务全部舍弃
// 若下次想执行取消前未执行的任务,就得重新添加任务
}
-----打印:
<NSThread: 0x600000dd1d00>{number = 3, name = (null)}-----1
<NSThread: 0x600000ddd7c0>{number = 4, name = (null)}-----0
<NSThread: 0x600000ddd180>{number = 5, name = (null)}-----2
<NSThread: 0x600000dbc740>{number = 6, name = (null)}-----3
<NSThread: 0x600000ddd7c0>{number = 4, name = (null)}-----4
<NSThread: 0x600000dbc740>{number = 6, name = (null)}-----5
<NSThread: 0x600000ddd180>{number = 5, name = (null)}-----6
<NSThread: 0x600000dbc740>{number = 6, name = (null)}-----7
当前为执行状态,准备挂起
<NSThread: 0x600000dd1d00>{number = 3, name = (null)}-----8
<NSThread: 0x600000dbc740>{number = 6, name = (null)}-----9
-----提问:
为何挂起后还有两次执行?
答:因为挂起的时刻之前的两次任务已经被调度,将会在线程中执行---已经被调度的任务无法挂起
小插曲:
队列中的任务一旦被调度,就离开了队列,然后等待cpu调度线程来执行,所产生的线程数与队列没有关系。主队列调度的任务由主线程执行,其他队列的任务由子线程执行。
三、NSOperation的应用
1、模仿一个最简单的SDWebImage
// 内存缓存 --- 比沙盒快 --- 但内存会被清理
UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage)
{
NSLog(@"从内存获取image:%@",model.title);
cell.imageView.image = cacheImage;
return cell;
}
// 沙盒缓存 --
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage)
{
NSLog(@"从沙盒获取image:%@",model.title);
cell.imageView.image = diskImage;
[self.imageCacheDict setValue:diskImage forKey:model.imageUrl];// 别忘了存入缓存
return cell;
}
// 下载 -- 事务
// 开辟子线程去 - 下载
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下载:%@",model.title);
// 延迟
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存内存
[self.imageCacheDict setValue:image forKey:model.imageUrl];
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;