一、NSOperation的基本概念
NSOperation是OC的,GCD是C语言的
1、NSOperation的作用
配合使用NSOperation和NSOperationQueue也能实现多线程编程,跟GCD的队列、任务很像。
2、NSOperation和NSOperationQueue实现多线程的具体步骤
- 先将需要执行的操作封装到一个NSOperation对象中
- 然后将NSOperation对象添加到NSOperationQueue中
- 系统会自动将NSOperationQueue中的NSOperation取出来
- 将取出的NSOperation封装的操作放到一条新线程中执行
与GCD相比,NSOperation没有串行、并行,同步、异步,的概念。
3、NSOperation的子类
(1)NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
(2)使用NSOperation子类的方式有3种
NSInvocationOperation
NSBlockOperation
自定义子类继承NSOperation,实现内部相应的方法
二、NSInvocationOperation
invocation :调度
1、创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
2、调用start方法开始执行操作
- (void)start;
一旦执行操作,就会调用target的sel方法
3、注意
默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。
只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。
4、默认情况
1 @interface MAViewController () 2 /** NSOperation操作队列 */ 3 @property (strong, nonatomic) NSOperationQueue *queue; 4 @end 5 6 @implementation MAViewController 7 8 // 将操作添加到队列即可 9 - (NSOperationQueue *)queue 10 { 11 if (!_queue) _queue = [[NSOperationQueue alloc] init]; 12 return _queue; 13 } 14 15 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 16 { 17 [self opDemo1]; 18 } 19 20 #pragma mark - NSOperation 21 22 - (void)download:(id)obj 23 { 24 NSLog(@"下载开始 %@ -- %@", [NSThread currentThread], obj); 25 } 26 27 - (void)opDemo1 28 { 29 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download:) object:@"op1"]; 30 // 如果直接启动,会在主线程执行
31 [op1 start];
32 }
输出结果:
下载开始 <NSThread: 0x8d65a90>{name = (null), num = 1} -- op1
5、将NSOperation(即NSInvocationOperation)放到NSOperationQueue中
修改opDemo1中的代码如下:
- (void)opDemo1 { NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download:) object:@"op1"]; // 添加到队列,就会新建线程,异步执行 [self.queue addOperation:op1]; }
输出结果:
下载开始 <NSThread: 0x8c720d0>{name = (null), num = 2} -- op1
结果说明,NSInvocationOperation操作放到了NSOperationQueue队列中,新建线程,异步执行。
6、多来几次添加到NSOperationQueue队列中
- (void)opDemo1 { for (int i = 0; i < 10; i++) { NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download:) object:@(i)]; // 添加到队列,就会新建线程,异步执行 [self.queue addOperation:op1]; } }
输出结果:
下载开始 <NSThread: 0x8d5ca80>{name = (null), num = 3} -- 1
下载开始 <NSThread: 0x8c800b0>{name = (null), num = 4} -- 2
下载开始 <NSThread: 0x8c7f8f0>{name = (null), num = 2} -- 0
下载开始 <NSThread: 0x8c80850>{name = (null), num = 5} -- 3
下载开始 <NSThread: 0x8d5ca80>{name = (null), num = 3} -- 6
下载开始 <NSThread: 0x8c800b0>{name = (null), num = 4} -- 4
下载开始 <NSThread: 0x8c80850>{name = (null), num = 5} -- 7
下载开始 <NSThread: 0x8c7f8f0>{name = (null), num = 2} -- 5
下载开始 <NSThread: 0x8c800b0>{name = (null), num = 4} -- 9
下载开始 <NSThread: 0x8d5ca80>{name = (null), num = 3} -- 8
说明NSInvocationOperation操作放到了NSOperationQueue队列中,是并行、异步的。
三、NSBlockOperation
1、创建 NSBlockOperation 对象
+ (id)blockOperationWithBlock:(void (^)(void))block;
2、把 NSBlockOperation 放到队列中
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self opDemo2]; } - (void)opDemo2 { NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载开始 %@ -- %@", [NSThread currentThread], nil); }]; [self.queue addOperation:op1]; }
输出结果:下载开始 <NSThread: 0xd14ae30>{name = (null), num = 2} -- (null)
多来几次,将 NSBlockOperation 对象添加到队列中
- (void)opDemo2 { for (int i = 0; i < 10; i++) { NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载开始 %@ -- %d", [NSThread currentThread], i); }]; [self.queue addOperation:op1]; } }
输出结果:
下载开始 <NSThread: 0x8d78b80>{name = (null), num = 2} -- 2
下载开始 <NSThread: 0x9b5fbe0>{name = (null), num = 5} -- 3
下载开始 <NSThread: 0x9b68e50>{name = (null), num = 3} -- 1
下载开始 <NSThread: 0x8cb1280>{name = (null), num = 4} -- 0
下载开始 <NSThread: 0x9b5fbe0>{name = (null), num = 5} -- 7
下载开始 <NSThread: 0x8d78b80>{name = (null), num = 2} -- 5
下载开始 <NSThread: 0x9b68e50>{name = (null), num = 3} -- 6
下载开始 <NSThread: 0x8cb1280>{name = (null), num = 4} -- 4
下载开始 <NSThread: 0x9b5fbe0>{name = (null), num = 5} -- 8
下载开始 <NSThread: 0x8d78b80>{name = (null), num = 2} -- 9
两次结果说明,将NSBlockOperation对象添加到NSOperationQueue中,是并行,异步的
而且,可以发现,NSBlockOperation比NSInvocationOperation操作更简单,少了一步调用download的操作,看上去更简洁
3、更简单的形式
- (void)opDemo3 { for (int i = 0; i < 10; i++) { [self.queue addOperationWithBlock:^{ NSLog(@"下载开始 %@ -- %d", [NSThread currentThread], i); }]; } }
输出结果:
下载开始 <NSThread: 0x8d78b80>{name = (null), num = 2} -- 2
下载开始 <NSThread: 0x9b5fbe0>{name = (null), num = 5} -- 3
下载开始 <NSThread: 0x9b68e50>{name = (null), num = 3} -- 1
下载开始 <NSThread: 0x8cb1280>{name = (null), num = 4} -- 0
下载开始 <NSThread: 0x9b5fbe0>{name = (null), num = 5} -- 7
下载开始 <NSThread: 0x8d78b80>{name = (null), num = 2} -- 5
下载开始 <NSThread: 0x9b68e50>{name = (null), num = 3} -- 6
下载开始 <NSThread: 0x8cb1280>{name = (null), num = 4} -- 4
下载开始 <NSThread: 0x9b5fbe0>{name = (null), num = 5} -- 8
下载开始 <NSThread: 0x8d78b80>{name = (null), num = 2} -- 9
结果还是并行,异步的。而且形式更加简单
4、向主队列中添加操作
- (void)opDemo3 { for (int i = 0; i < 10; i++) { // 只要将操作添加到队列,就会立即被调度(执行) [self.queue addOperationWithBlock:^{ NSLog(@"下载开始 %@ -- %d", [NSThread currentThread], i); }]; } // 向主队列中添加操作 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"下载开始 %@ -- %@", [NSThread currentThread], nil); }]; }
输出结果:
下载开始 <NSThread: 0x8c7f480>{name = (null), num = 5} -- 1
下载开始 <NSThread: 0x8c76710>{name = (null), num = 1} -- (null)
下载开始 <NSThread: 0x8e70eb0>{name = (null), num = 3} -- 2
下载开始 <NSThread: 0x8e71e20>{name = (null), num = 4} -- 3
下载开始 <NSThread: 0x8c7b030>{name = (null), num = 2} -- 0
下载开始 <NSThread: 0x8c7f480>{name = (null), num = 5} -- 4
下载开始 <NSThread: 0x8e70eb0>{name = (null), num = 3} -- 5
下载开始 <NSThread: 0x8e711e0>{name = (null), num = 6} -- 6
下载开始 <NSThread: 0x8e71e20>{name = (null), num = 4} -- 7
下载开始 <NSThread: 0x8e70eb0>{name = (null), num = 3} -- 9
下载开始 <NSThread: 0x8c7f480>{name = (null), num = 5} -- 8
可以看到向主队列添加的操作与子线程中的操作是同时执行的,而不是子线程执行完才执行主线程
5、通过addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作。
(1)通过代码来研究 addExecutionBlock 的作用:
// 实例化block操作 NSBlockOperation *op = [[NSBlockOperation alloc] init]; // 添加执行块 [op addExecutionBlock:^{ NSLog(@"下载书籍 %@", [NSThread currentThread]); }]; [op start];
输出结果:下载书籍 <NSThread: 0x8f22920>{name = (null), num = 1}
可以看到 NSBlockOperation封装的操作数 = 1 就会在主线程执行
(2)如果大于1,呢?
// 实例化block操作 NSBlockOperation *op = [[NSBlockOperation alloc] init]; // 添加执行块 [op addExecutionBlock:^{ NSLog(@"下载书籍1 %@", [NSThread currentThread]); }]; // 继续添加块 [op addExecutionBlock:^{ NSLog(@"下载书籍2 %@", [NSThread currentThread]); }]; // 继续添加块 [op addExecutionBlock:^{ NSLog(@"下载书籍3 %@", [NSThread currentThread]); }]; // 继续添加块 [op addExecutionBlock:^{ NSLog(@"下载书籍4 %@", [NSThread currentThread]); }]; // 继续添加块 [op addExecutionBlock:^{ NSLog(@"下载书籍5 %@", [NSThread currentThread]); }]; [op start];
输出结果:
多线程调试,永远不要相信一次的运行结果,再次运行结果:
这两个结果说明,只要NSBlockOperation封装的操作数 > 1,就会异步执行操作。而且执行块不一定哪个会被放到主线程。
(3)如果添加到 NSOperationQueue 队列中
// [op start]; [self.queue addOperation:op];
输出结果:
结果说明,放到队列中就全部异步执行了,直接启动是有在主线程,有在子线程的。具体开启线程的数量,由系统决定。
执行块的调度与操作的调度非常像。