NSOperation
1、基本概念
NSOperation基于GCD开发,但是比GCD有更好的可控性和代码可读性。
NSOperation本身是一个抽象类,使用更多的是系统封装好的NSInvocationOperation 和 NSBlockOperation。
2、NSOperation通用方法
NSOperation *operation = [[NSOperation alloc] init];
[operation start]; //开始执行
[operation cancel]; //取消执行
[operation setCompletionBlock:^{ //执行结束后调用的block
}];
NSInvocationOperation
- (void)viewDidLoad {
[super viewDidLoad];
/*
第一个参数:目标对象
第二个参数:该操作要调用的方法,最多接受一个参数;
如果selector有返回值,可以在调用结束后通过'result'方法取得返回值
第三个参数:调用方法传递的参数,如果方法不接受参数,那么该值传nil
*/
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(showmsg) object:nil];
//执行操作
[operation start];
//打印block的返回值
NSLog(@"%@", operation.result);
}
-(NSNumber *)showmsg
{
NSLog(@"%zd", [NSThread currentThread]);
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"%zd", i);
}
return @100;
}
输出结果:
2017-04-28 13:43:56.999 newTest[4410:2043571] <NSThread: 0x12ee0c860>{number = 1, name = main}
2017-04-28 13:43:56.999 newTest[4410:2043571] 0
2017-04-28 13:43:57.000 newTest[4410:2043571] 1
2017-04-28 13:43:57.000 newTest[4410:2043571] 2
2017-04-28 13:43:57.000 newTest[4410:2043571] 3
2017-04-28 13:43:57.000 newTest[4410:2043571] 4
2017-04-28 13:43:57.000 newTest[4410:2043571] 5
2017-04-28 13:43:57.000 newTest[4410:2043571] 6
2017-04-28 13:43:57.000 newTest[4410:2043571] 7
2017-04-28 13:43:57.000 newTest[4410:2043571] 8
2017-04-28 13:43:57.001 newTest[4410:2043571] 9
2017-04-28 13:43:57.001 newTest[4410:2043571] 100
NSInvocationOperation其实是同步执行的,因此单独使用的话,这个东西也没有什么卵用,它需要配合我们后面介绍的NSOperationQueue去使用才能实现多线程调用。
NSBlockOperation
(1) NSBlockOperation也是NSOperation的子类,支持并发的实行一个或多个block
(2)NSBlockOperation优先将block放到主线程中执行,若主线程已有待执行的代码,就开辟新的线程,但最大并发数为4(包括主线程在内)。
如果block数量大于了4,那么剩下的Block就会等待某个线程空闲下来之后被分配到该线程,且依然是优先分配到主线程.
(3)同一个block中的代码是同步执行的.
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"111111%@" ,[NSThread currentThread]);
NSLog(@"block1");
}];
[operation addExecutionBlock:^{
NSLog(@"2222222%@" ,[NSThread currentThread]);
NSLog(@"block2");
}];
[operation addExecutionBlock:^{
NSLog(@"33333%@" ,[NSThread currentThread]);
NSLog(@"block3");
}];
[operation addExecutionBlock:^{
NSLog(@"444444%@" ,[NSThread currentThread]);
NSLog(@"block4");
}];
[operation addExecutionBlock:^{
NSLog(@"555555%@" ,[NSThread currentThread]);
NSLog(@"block5");
}];
[operation addExecutionBlock:^{
NSLog(@"6666666%@" ,[NSThread currentThread]);
NSLog(@"block6");
}];
[operation addExecutionBlock:^{
NSLog(@"7777777%@" ,[NSThread currentThread]);
NSLog(@"block7");
}];
[operation start];
执行结果:
2017-04-28 14:35:17.855 newTest[4483:2062648] 111111<NSThread: 0x14e604540>{number = 1, name = main}
2017-04-28 14:35:17.855 newTest[4483:2062732] 2222222<NSThread: 0x14e6122c0>{number = 3, name = (null)}
2017-04-28 14:35:17.856 newTest[4483:2062648] block1
2017-04-28 14:35:17.856 newTest[4483:2062648] 33333<NSThread: 0x14e604540>{number = 1, name = main}
2017-04-28 14:35:17.856 newTest[4483:2062648] block3
2017-04-28 14:35:17.856 newTest[4483:2062648] 444444<NSThread: 0x14e604540>{number = 1, name = main}
2017-04-28 14:35:17.856 newTest[4483:2062648] block4
2017-04-28 14:35:17.856 newTest[4483:2062648] 555555<NSThread: 0x14e604540>{number = 1, name = main}
2017-04-28 14:35:17.856 newTest[4483:2062648] block5
2017-04-28 14:35:17.856 newTest[4483:2062648] 6666666<NSThread: 0x14e604540>{number = 1, name = main}
2017-04-28 14:35:17.856 newTest[4483:2062648] block6
2017-04-28 14:35:17.856 newTest[4483:2062648] 7777777<NSThread: 0x14e604540>{number = 1, name = main}
2017-04-28 14:35:17.856 newTest[4483:2062648] block7
2017-04-28 14:35:17.857 newTest[4483:2062732] block2
NSOPerationQueue
NSOperation中的两种队列
01 主队列 通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
02 非主队列 直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"111111%@" ,[NSThread currentThread]);
NSLog(@"block1");
}];
[operation addExecutionBlock:^{
NSLog(@"2222222%@" ,[NSThread currentThread]);
NSLog(@"block2");
}];
[operation addExecutionBlock:^{
NSLog(@"33333%@" ,[NSThread currentThread]);
NSLog(@"block3");
}];
NSInvocationOperation *invoOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(showmsg) object:nil];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//添加operation
[queue addOperation:operation];
[queue addOperation:invoOperation];
[queue addOperationWithBlock:^{
NSLog(@"4444444%@" ,[NSThread currentThread]);
NSLog(@"block4");
}];
2017-04-28 14:45:38.478 newTest[4494:2065023] showmsg --- <NSThread: 0x14761a7b0>{number = 2, name = (null)}
2017-04-28 14:45:38.478 newTest[4494:2065036] 111111<NSThread: 0x1475426d0>{number = 3, name = (null)}
2017-04-28 14:45:38.478 newTest[4494:2065035] 2222222<NSThread: 0x14765ae60>{number = 4, name = (null)}
2017-04-28 14:45:38.480 newTest[4494:2065036] block1
2017-04-28 14:45:38.480 newTest[4494:2065035] block2
2017-04-28 14:45:38.481 newTest[4494:2065036] 33333<NSThread: 0x1475426d0>{number = 3, name = (null)}
2017-04-28 14:45:38.481 newTest[4494:2065036] block3
2017-04-28 14:45:38.479 newTest[4494:2065030] 4444444<NSThread: 0x1475c7a40>{number = 5, name = (null)}
2017-04-28 14:45:38.484 newTest[4494:2065030] block4
从结果中可以看到
NSInvocationOperation 和 NSBlockOperation是异步执行的;
NSBlockOperation中的每一个Block也是异步执行且都在子线程中执行;
每一个Block内部也依然是同步执行。
最大并发数 maxConcurrentOperationCount
(1)该属性需要在任务添加到队列中之前进行设置;
(2)如果最大并发数等于1,那么该队列是串行的,如果大于1那么是并行的;
(4)系统的最大并发数有个默认的值,为-1,如果该属性设置为0,那么不会执行任何任务;
队列的最大并发操作数量,意思是队列中最多同时运行几条线程.
虽然NSOperationQueue类设计用于并发执行Operations,你也可以强制单个queue一次只能执行一个Operation。
setMaxConcurrentOperationCount:方法可以配置queue的最大并发操作数量。
设为1就表示queue每次只能执行一个操作。
不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。
因此串行化的operation queue并不等同于GCD中的串行dispatch queue
暂停、恢复、取消
如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。
不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"111111%@" ,[NSThread currentThread]);
NSLog(@"block1");
}];
[operation addExecutionBlock:^{
NSLog(@"2222222%@" ,[NSThread currentThread]);
NSLog(@"block2");
}];
[operation addExecutionBlock:^{
NSLog(@"33333%@" ,[NSThread currentThread]);
NSLog(@"block3");
}];
NSInvocationOperation *invoOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(showmsg) object:nil];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation];
NSLog(@"--------suspended");
queue.suspended = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"--------no suspended");
queue.suspended = NO;
});
[queue addOperation:invoOperation];
[queue addOperationWithBlock:^{
NSLog(@"4444444%@" ,[NSThread currentThread]);
NSLog(@"block4");
}];
运行结果:
2017-04-28 15:13:34.822 newTest[4533:2073610] 111111<NSThread: 0x135eb28c0>{number = 4, name = (null)}
2017-04-28 15:13:34.822 newTest[4533:2073609] 2222222<NSThread: 0x135d28200>{number = 3, name = (null)}
2017-04-28 15:13:34.823 newTest[4533:2073609] block2
2017-04-28 15:13:34.823 newTest[4533:2073609] 33333<NSThread: 0x135d28200>{number = 3, name = (null)}
2017-04-28 15:13:34.823 newTest[4533:2073609] block3
2017-04-28 15:13:34.824 newTest[4533:2073610] block1
2017-04-28 15:13:34.822 newTest[4533:2073538] --------suspended
2017-04-28 15:13:35.903 newTest[4533:2073538] --------no suspended
2017-04-28 15:13:35.904 newTest[4533:2073609] showmsg --- <NSThread: 0x135d28200>{number = 3, name = (null)}
2017-04-28 15:13:35.904 newTest[4533:2073610] 4444444<NSThread: 0x135eb28c0>{number = 4, name = (null)}
2017-04-28 15:13:35.905 newTest[4533:2073610] block4
调用 queue.suspended = YES; 之前添加的operation都能执行,之后的操作会暂停;
调用 queue.suspended = NO; 之后继续添加任务到队列,都可以执行;
取消队列里面的所有操作
取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远都不在执行,就像后面的所有任务都从队列里面移除了一样
取消操作是不可以恢复的
[operation cancel];
[self.queue cancelAllOperations];
添加依赖关系
当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象.
使用依赖关系有三点需要注意
1.不要建立循环依赖,会造成死锁,原因同循环引用
2.依赖关系不光在同队列中生效,不同队列的NSOperation对象之前设置的依赖关系一样会生效
没有依赖关系,operation执行是没有顺序的;
NSBlockOperation *ope1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block1");
}];
NSBlockOperation *ope2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block2");
}];
NSBlockOperation *ope3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block3");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:ope1];
[queue addOperation:ope2];
[queue addOperation:ope3];
输出结果:
2017-04-28 15:51:20.245 newTest[4603:2087102] block1
2017-04-28 15:51:20.245 newTest[4603:2087108] block3
2017-04-28 15:51:20.245 newTest[4603:2087111] block2
添加依赖
NSBlockOperation *ope1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block1");
}];
NSBlockOperation *ope2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block2");
}];
NSBlockOperation *ope3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block3");
}];
//ope1 依赖 ope2,ope2执行完后才会执行ope1
[ope1 addDependency:ope2];
//ope2 依赖 ope3,ope3执行完后才会执行ope2
[ope2 addDependency:ope3];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:ope1];
[queue addOperation:ope2];
[queue addOperation:ope3];
输出结果:
2017-04-28 15:53:40.599 newTest[4606:2087853] block3
2017-04-28 15:53:40.600 newTest[4606:2087853] block2
2017-04-28 15:53:40.600 newTest[4606:2087853] block1
监听任务
通过设置operation的 completionBlock,可以件监听任务的结束;
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"------option3");
}];
op3.completionBlock = ^{
NSLog(@"---op3 is end");
};
阻塞线程
- 调用NSOperationQueue的 waitUntilAllOperationsAreFinished可以阻塞当前队列,直到所有operation执行完毕;
[queue waitUntilAllOperationsAreFinished];
-
还可以调用NSOperation的waitUntilFinished方法,阻塞当前线程
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ for(int i = 0; i < 10; i++){ NSLog(@"------option1"); } }]; NSBlockOperation *opt = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"wait option1 end"); [op1 waitUntilFinished]; NSLog(@"option1 is end"); }]; [queue addOperation:opt]; [queue addOperation:opt1]; opt剩余的操作会等待op1执行完毕后再继续执行;
Operations设置优先级
对于添加到queue中的operations,它们的执行顺序取决于2点:
1.首先看看NSOperation是否已经准备好:是否准备好由对象的依赖关系确定
2.然后再根据所有NSOperation的相对优先级来确定。
优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operation queue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。
注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。
线程优先级代表的是线程获取CPU时间片的能力,高优先级的执行概率高,不是执行顺序靠前。cpu会尽力将资源给高优先级的,尽量使高优先级的先执行,但优先级具有随机行,并不是高的就一定先执行.