多线程(三)之NSOperation
//与GCD使用的步骤一样,并且也不需要管理线程,可调度线程池负责线程的创建回收优化. //1.创建操作 //NSOperation是抽象类,不能直接使用,只有定义没有实现,因为是抽象类,使用子类或者系统已经定义好的2个子类 //(NSInvocationOperation or NSBlockOperation)
1.定义:
1.小结:定义:一旦提到NSOperation就要想到,这个类是个抽象类,什么是抽象类,就是我们不会具体去用他,而是去用他的子类去实例化对象
NSOperation有两个子类:NSInvocationOperation和NSBlockOperation
首先是NSInvocationOperation的用法:
- (void)demo1 { //与GCD使用的步骤一样,并且也不需要管理线程,可调度线程池负责线程的创建回收优化. //1.创建操作 //NSOperation是抽象类,不能直接使用,只有定义没有实现,因为是抽象类,使用子类或者系统已经定义好的2个子类 //(NSInvocationOperation or NSBlockOperation) /* NSInvocationOperation 年代比较久远,一般不用 1.对象 2.方法 3.参数 */ NSInvocationOperation *innvocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil]; //Begins the execution of the operation.在当前线程运行 // [innvocationOp start]; //2.队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //3.把操作+到队列中 [queue addOperation:innvocationOp]; }
NSBlockOperation的用法一:基本用法:
和GCD 一样,只有将操作加到队列中去执行才能开线程,否则还是在主线程中执行
-(void)demo2{ //NSBlockOperation //参数是一个block,回调 NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"task2 is running %@",[NSThread currentThread]); }]; // [blockOp start]; //2.队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //3.把操作+到队列中 [queue addOperation:blockOp]; }
NSBlockOperation的用法二:常用方法,即基本用法的简化版本:直接把操作加入到队列中默认是并发队列异步执行
-(void)demo4{ //开发中常用写法 //直接把一个blockOperation+到队列 //操作无序,多个线程->并发队列,异步执行!!!! for (int i = 0; i<20; i++) { [self.queue addOperationWithBlock:^{ NSLog(@"task2 is running %@ %d",[NSThread currentThread],i); }]; } }
2.最大并发数
最大并发数定义:
什么是最大并发数?
上图是我模拟线程处理操作的流程图:
即操作添加到队列中,队列会从可调度线程池申请线程,可调度线程池就会开一条线程,队列调度操作在此线程上执行,如果设置了最大操作并发数,就是指可调度线程池最多能开的线程数.
体现在代码上假设我设置的最大并发数是2,那么就是最多只能有两个操作同时异步并发操作,也就是会两个两个的打印,如下所示:看打印结果的时间
// // ViewController.m // 最大并发数 // // Created by apple on 16/8/22. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" @interface ViewController () @property(nonatomic,strong) NSOperationQueue *queue; @end @implementation ViewController -(NSOperationQueue *)queue{ if(!_queue){ _queue = [[NSOperationQueue alloc] init]; //设置最大并发数:同一时间,只能够有哦固定数量的子线程去执行队列中的操作 _queue.maxConcurrentOperationCount = 2; } return _queue; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self demo2]; } -(void)demo2{ //开启了至少60+线程 for (int i = 0; i<99; i++) { [self.queue addOperationWithBlock:^{ [NSThread sleepForTimeInterval:1]; NSLog(@"%d %@",i,[NSThread currentThread]); }]; } } //错误代码 - (void)demo1 { //使用GCD,异步并发,很多任务->线程开的太多,资源损耗,GCD不能法规定开启的线程数量 for (int i = 0; i<999999; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"%d %@",i,[NSThread currentThread]); }); } } @end
2016-12-13 17:16:36.995 最大并发数[10417:1467862] 0 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:36.995 最大并发数[10417:1467863] 1 <NSThread: 0x600000267180>{number = 4, name = (null)} 2016-12-13 17:16:37.996 最大并发数[10417:1467862] 2 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:37.996 最大并发数[10417:1467863] 3 <NSThread: 0x600000267180>{number = 4, name = (null)} 2016-12-13 17:16:39.070 最大并发数[10417:1467862] 4 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:39.070 最大并发数[10417:1467863] 5 <NSThread: 0x600000267180>{number = 4, name = (null)} 2016-12-13 17:16:40.140 最大并发数[10417:1467865] 7 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:40.140 最大并发数[10417:1467862] 6 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:41.205 最大并发数[10417:1467871] 8 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:41.204 最大并发数[10417:1467865] 9 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:42.205 最大并发数[10417:1467871] 11 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:42.205 最大并发数[10417:1467862] 10 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:43.274 最大并发数[10417:1467862] 13 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:43.274 最大并发数[10417:1467871] 12 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:44.348 最大并发数[10417:1467862] 14 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:44.348 最大并发数[10417:1467871] 15 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:45.423 最大并发数[10417:1467871] 17 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:45.423 最大并发数[10417:1467862] 16 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:46.491 最大并发数[10417:1467871] 18 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:46.491 最大并发数[10417:1467862] 19 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:47.491 最大并发数[10417:1467862] 20 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:47.492 最大并发数[10417:1467871] 21 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:48.493 最大并发数[10417:1467862] 22 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:48.493 最大并发数[10417:1467865] 23 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:49.494 最大并发数[10417:1467862] 24 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:49.494 最大并发数[10417:1467865] 25 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:50.560 最大并发数[10417:1467865] 27 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:50.560 最大并发数[10417:1467862] 26 <NSThread: 0x600000264d40>{number = 3, name = (null)} 2016-12-13 17:16:51.628 最大并发数[10417:1467865] 29 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:51.628 最大并发数[10417:1467871] 28 <NSThread: 0x60000007b9c0>{number = 6, name = (null)} 2016-12-13 17:16:52.696 最大并发数[10417:1467867] 30 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:16:52.696 最大并发数[10417:1467865] 31 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:53.771 最大并发数[10417:1467867] 32 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:16:53.771 最大并发数[10417:1467865] 33 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:54.837 最大并发数[10417:1467865] 35 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:54.837 最大并发数[10417:1467867] 34 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:16:55.910 最大并发数[10417:1467865] 36 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:55.910 最大并发数[10417:1467867] 37 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:16:56.972 最大并发数[10417:1467865] 38 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:56.972 最大并发数[10417:1467867] 39 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:16:58.047 最大并发数[10417:1467867] 41 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:16:58.047 最大并发数[10417:1467865] 40 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:59.112 最大并发数[10417:1467865] 43 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:16:59.112 最大并发数[10417:1467867] 42 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:17:00.182 最大并发数[10417:1467865] 44 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:17:00.182 最大并发数[10417:1467867] 45 <NSThread: 0x608000075d40>{number = 7, name = (null)} 2016-12-13 17:17:01.253 最大并发数[10417:1467865] 46 <NSThread: 0x60800007e380>{number = 5, name = (null)} 2016-12-13 17:17:01.253 最大并发数[10417:1467871] 47 <NSThread: 0x60000007b9c0>{number = 6, name = (null)}
小结:通过代码demo1也说明了GCD不具备管理线程的能力,这也是NSOperation比GCD多的一个优点
3.开启,暂停,继续,取消
// // ViewController.m // 暂停,取消,继续 // // Created by apple on 16/8/22. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" @interface ViewController () @property(nonatomic,strong) NSOperationQueue *queue; @end @implementation ViewController //开启->暂停->取消->开启 不运行 -(NSOperationQueue *)queue{ if(!_queue){ _queue = [[NSOperationQueue alloc] init]; //最大并发数 _queue.maxConcurrentOperationCount = 2; } return _queue; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (IBAction)beginClick:(id)sender { NSLog(@"开启"); for (int i = 0; i<99; i++) { [self.queue addOperationWithBlock:^{ [NSThread sleepForTimeInterval:1]; NSLog(@"%d %@",i,[NSThread currentThread]); }]; }
//for循环这种,记住都是把操作全部添加到队列中,再执行的 } - (IBAction)pauseClick:(id)sender { NSLog(@"暂停"); //暂停的是队列中还没有执行的操作,已经执行的操作不能被打断,暂停 [self.queue setSuspended:YES]; } - (IBAction)resumeClick:(id)sender { NSLog(@"继续"); [self.queue setSuspended:NO]; } - (IBAction)cancelClick:(id)sender { NSLog(@"取消"); //取消的是队列中还没有执行的操作,已经执行的操作不能取消 //队列已经被挂起,在往队列中假如操作不能运行,需要setSuspended:NO [self.queue cancelAllOperations]; } @end
4.模拟tiger的思路
先进行界面布局,点击开始按钮,把任务添加到队列,while死循环执行随机数,判断是否是开启状态,如果是,就在主线程添加给控件赋值随机数的操作
这里是用if判断来进行开始和暂停
// // ViewController.m //// // Created by apple on 16/8/22. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" /* 1.搭建界面 2.写一个方法,让数字动起来 3.子线程运行randomDispaly 4.UI更新->主线程 */ @interface ViewController () @property (weak, nonatomic) IBOutlet UILabel *label1; @property (weak, nonatomic) IBOutlet UILabel *label2; @property (weak, nonatomic) IBOutlet UILabel *label3; @property (weak, nonatomic) IBOutlet UIButton *btn; @property(nonatomic,strong) NSOperationQueue *queue; @end @implementation ViewController -(NSOperationQueue *)queue{ if(!_queue){ _queue = [[NSOperationQueue alloc] init]; } return _queue; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (IBAction)btnClick:(id)sender { //1.判断是否开启了操作,当前队列中只有一个操作,并且正在执行,不能通过setSuspended停止操作,通过操作数量来判断是否已经执行 //2.停止正在执行的操作 if(self.queue.operationCount == 0){ //开启操作 [self.queue addOperationWithBlock:^{ [self randomDispaly]; }]; [self.btn setTitle:@"暂停" forState:UIControlStateNormal]; [self.queue setSuspended:NO]; }else{ //setSuspended 方法本身不能够停止一个正在执行的操作,只是改变了属性的值,通过值的判断,在while条件判断,让循环退出 [self.btn setTitle:@"开始" forState:UIControlStateNormal]; [self.queue setSuspended:YES]; } } //随机显示数字到label上,模拟tiger机轮动起来的效果 //当while循环的时候,成为一个`耗时任务`,要在子线程运行 - (void)randomDispaly { // NSLog(@"%@",[NSThread currentThread]); //[0~10) while (![self.queue isSuspended]) { //变得慢一点 //不写这句话,暂停一时半会儿停不了 i/o //由于屏幕是IO设备,速度很慢,即使在内存CPU已经把队列暂停了,但是他还在处理之前没有显示的任务 [NSThread sleepForTimeInterval:0.1]; int randomNumber1 = arc4random_uniform(10); int randomNumber2 = arc4random_uniform(10); int randomNumber3 = arc4random_uniform(10); //回到主线程去运行,线程间通信 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.label1.text = [NSString stringWithFormat:@"%d",randomNumber1]; self.label2.text = [NSString stringWithFormat:@"%d",randomNumber2]; self.label3.text = [NSString stringWithFormat:@"%d",randomNumber3]; }]; } } @end
5.优先级:优先级高的不一定比优先级低的先执行,只是有更多的可能性被CPU 调用
优先级分为操作优先级和队列优先级:
操作优先级:提高某一个操作的优先级,看先执行哪个操作
- (void)demo1 { for (int i = 0; i<20; i++) { NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%d %@",i,[NSThread currentThread]); }]; //提高某一个操作的优先级 if(i == 10){ //被废弃 // [op setQueuePriority:NSOperationQueuePriorityVeryHigh]; //IOS8 [op setQualityOfService:NSQualityOfServiceUserInitiated]; } [self.queue addOperation:op]; } }
队列优先级:在队列初始化的时候设置队列的优先级,看打印结果
//优先级:高的不一定比低的先运行,有更多的可能性被CPU调用 -(NSOperationQueue *)queue{ if(!_queue){ _queue = [[NSOperationQueue alloc] init]; //设置队列的优先级 [_queue setQualityOfService:NSQualityOfServiceBackground]; } return _queue; } -(NSOperationQueue *)queue1{ if(!_queue1){ _queue1 = [[NSOperationQueue alloc] init]; [_queue1 setQualityOfService:NSQualityOfServiceUserInteractive]; } return _queue1; } - (void)viewDidLoad { [super viewDidLoad]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self demo2]; } //队列的优先级 -(void)demo2{ for (int i = 0; i<20; i++) { NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"----> %d %@",i,[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@">>>>> %d %@",i,[NSThread currentThread]); }]; [self.queue addOperation:op]; [self.queue1 addOperation:op2]; } }
6.设置依赖关系
由于NSOperation是默认异步并发执行操作,不像GCD可以设置同步执行,按顺序一步步来,那么有没有办法可以做到NSOperation操作同步执行呢?
下面就是NSOperation的同步方法:设置依赖
// // ViewController.m // 下载音乐Demo // // Created by apple on 16/8/22. // Copyright © 2016年 itcast. All rights reserved. // #import "ViewController.h" @interface ViewController () @property(nonatomic,strong) NSOperationQueue *queue; @end @implementation ViewController -(NSOperationQueue *)queue{ if(!_queue){ _queue = [[NSOperationQueue alloc] init]; } return _queue; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self demo1]; } - (void)demo1 { /* 1.登陆 2.付款 3.下载 4.UI更新 1,2,3,4有序->GCD同步的方式 1,2,3耗时任务->子线程 4->主线程 */ NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1.登陆%@",[NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"2.付款%@",[NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"3.下载%@",[NSThread currentThread]); }]; NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"4.UI更新%@",[NSThread currentThread]); }]; //设置依赖关系.可以跨队列设置,一定要在操作+到队列之间设置 [op2 addDependency:op1]; [op3 addDependency:op2]; [op4 addDependency:op3]; //避免头尾相接的循环依赖,死锁 // [op1 addDependency:op4]; [self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO]; //主队列 [[NSOperationQueue mainQueue] addOperation:op4]; } @end
重点来了:NSOperation的综合练习:图片的异步下载思路: