NSOperation、NSOperationQueue

####一、 为什么要使用 NSOperation、NSOperationQueue
NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
可添加完成的代码块,在操作完成后执行。
添加操作之间的依赖关系,方便的控制执行顺序。

设定操作执行的优先级。
可以很方便的取消一个操作的执行。
使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

####二、同步异步以及并行串行的区别
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态
并行串行是指有多个线程的任务的执行方式,串行则多个任务按顺序执行,并行则多个任务会同时执行

三、 NSOperation 实现多线程的使用步骤分为三步:

NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。

1、创建操作:先将需要执行的操作封装到一个NSOperation 对象中。
2、创建队列:创建NSOperationQueue对象。
3、将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。

之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。

####四、线程死锁

1
2
3
4
5
//    NSLog(@"1");
// dispatch_sync(dispatch_get_main_queue(), ^{
// NSLog(@"2");
// });
// NSLog(@"3");

主线程是串行队列, 一个任务执行完成才能往下执行, 同步线程是一个任务A ,里面的block是同步线程插入到当前线程的另一个任务B ,A要等B执行完才返回 ,B要等A执行完才能执行 ,会相互等待 造成死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

// 创建操作
// [self useInvocationOperation];
//// NSOperation单独使用 同步执行 等待结果返回后执行后面语句
// NSLog(@"下一个语句");

// [self useBlockOperation];

// 创建队列
// [self addOperationToQueue];

// 添加依赖
// [self addDependency];

// 优先级
// [self queuePriority];

// 线程间通讯
// [self communication];


// 线程死锁:主线程是串行队列 一个任务执行完成才能往下执行 同步线程是一个任务A 里面的block是同步线程插入到当前线程的另一个任务B A要等B执行完才返回 B要等A执行完才能执行 会相互等待 造成死锁
// NSLog(@"1");
// dispatch_sync(dispatch_get_main_queue(), ^{
// NSLog(@"2");
// });
// NSLog(@"3");
}


#pragma mark - 创建操作
//NSOperation 单独使用时系统同步执行操作
//NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。我们有三种方式来封装操作。
//使用子类 NSInvocationOperation
//使用子类 NSBlockOperation
//自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作。
- (void)useInvocationOperation{
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask) object:nil];

// 开始操作
[invocationOperation start];
}

//NSBlockOperation 还提供了一个方法 addExecutionBlock:,通过 addExecutionBlock: 就可以为 NSBlockOperation 添加额外的操作。这些操作(包括 blockOperationWithBlock 中的操作)可以在不同的线程中同时(并发)执行(完成顺序是不一定的)。只有当所有相关的操作已经完成执行时,才视为完成

//一般情况下,如果一个 NSBlockOperation 对象封装了多个操作。NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。
- (void)useBlockOperation{
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"blockOperation---%@", [NSThread currentThread]); // 打印当前线程
}

}];

// 2.添加额外的操作
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"6---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"7---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"8---%@", [NSThread currentThread]); // 打印当前线程
}
}];



// 调用 start 方法开始执行操作
[blockOperation start];
}
- (void)useCustomOperation{

}

#pragma mark - 创建队列
//NSOperationQueue 创建的自定义队列同时具有串行、并发功能,通过设置属性maxConcurrentOperationCount(最大并发操作数)用来控制一个特定队列中可以有多少个操作同时参与并发执行。
//当最大并发操作数为1时,操作是按顺序串行执行的,并且一个操作完成之后,下一个操作才开始执行。当最大操作并发数为2时,操作是并发执行的,可以同时执行两个操作。而开启线程数量是由系统决定的,不需要我们来管理


//那么我们需要将创建好的操作加入到队列中去。总共有两种方法:
//1、- (void)addOperation:(NSOperation *)op;
//2、- (void)addOperationWithBlock:(void (^)(void))block;

/**
* 使用 addOperation: 将操作加入到操作队列中
*/
- (void)addOperationToQueue {

// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大并发操作数 来控制队列的串行或者并发
queue.maxConcurrentOperationCount = 1;
// queue.maxConcurrentOperationCount = 8;
// 2.创建操作
// 使用 NSInvocationOperation 创建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask) object:nil];

// 使用 NSInvocationOperation 创建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask2) object:nil];

op1.queuePriority = NSOperationQueuePriorityHigh;
op2.queuePriority = NSOperationQueuePriorityLow;

// 使用 NSBlockOperation 创建操作3
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op3 addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
}
}];

// 3.使用 addOperation: 添加所有操作到队列中
[queue addOperation:op1]; // [op1 start] 添加到队列中 无需再自己调用start
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}

/**
* 使用 addOperationWithBlock: 将操作加入到操作队列中
无需自己创建操作对象 直接将操作放到block中
*/

- (void)addOperationWithBlockToQueue {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 2.使用 addOperationWithBlock: 添加操作到队列中
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}

#pragma mark - NSOperation 操作依赖
//NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。
//- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
//- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
//@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

- (void)addDependency {

// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 2.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];

// 3.添加依赖
[op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2

// 4.添加操作到队列中
[queue addOperation:op1];
[queue addOperation:op2];
}

#pragma mark - NSOperation 优先级 & 服务质量(只是尽可能的 并不是绝对的)
//NSOperation 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal。但是我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。

//对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)

//准备就绪状态:当一个操作的所有依赖都已经完成时,操作对象通常会进入准备就绪状态,等待执行

//queuePriority 属性(尽可能的并不是绝对的)决定了进入准备就绪状态下的操作之间的开始执行顺序。并且,优先级不能取代依赖关系。
//如果一个队列中既包含高优先级操作,又包含低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。比如上例中,如果 op1 和 op4 是不同优先级的操作,那么就会先执行优先级高的操作。
//如果,一个队列中既包含了准备就绪状态的操作,又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。那么,虽然准备就绪的操作优先级低,也会优先执行。优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系

- (void)queuePriority{
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大并发操作数 来控制队列的串行或者并发
// queue.maxConcurrentOperationCount = 1; //串行
// queue.maxConcurrentOperationCount = 8;//并发
// 2.创建操作
// 使用 NSInvocationOperation 创建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask) object:nil];

// 使用 NSInvocationOperation 创建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask2) object:nil];

// 使用 NSInvocationOperation 创建操作2
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask3) object:nil];

// 同一个队列中的操作优先级 执行的先后 并不是结束的先后
// op1.queuePriority = NSOperationQueuePriorityVeryHigh;
// op2.queuePriority = NSOperationQueuePriorityHigh;
// op3.queuePriority = NSOperationQueuePriorityNormal;

// 服务质量 在iOS 8.0前,通过设置操作的优先级,尽可能的保证某个操作优先处理,随着硬件性能上的提升,通过设置优先级效果已经越来越不明显,在iOS 8.0后,推出了服务质量,通过设置服务质量,让系统优先处理某一个操作 目前也是越来越不明显
// op1.qualityOfService = NSQualityOfServiceUserInteractive;
// op2.qualityOfService = NSQualityOfServiceBackground;
// op3.qualityOfService = NSQualityOfServiceDefault;

// 3.使用 addOperation: 添加所有操作到队列中
[queue addOperation:op1]; // [op1 start] 添加到队列中 无需再自己调用start
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}

#pragma mark - NSOperation、NSOperationQueue 线程间的通信
/**
* 线程间通信
*/
- (void)communication {

// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];

// 2.添加操作
[queue addOperationWithBlock:^{
// 异步进行耗时操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}

// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 进行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
}];
}];
}


#pragma mark - 执行的操作
- (void)invocationTask{
NSLog(@"1");
for (int i = 0; i < 2; i++) {
NSLog(@"invocationOperation1---%@", [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
}
}

- (void)invocationTask2{
NSLog(@"2");
for (int i = 0; i < 2; i++) {
NSLog(@"invocationOperation2---%@", [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
}
}

- (void)invocationTask3{
NSLog(@"3");
for (int i = 0; i < 2; i++) {
NSLog(@"invocationOperation3---%@", [NSThread currentThread]); // 打印当前线程
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
}
}
@end