学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

iOS中的多线程NSOperation

NSOperation 是 OC 语言中基于 GCD 的面向对象的封装

NSOperation 与 GCD 的区别

NSOperation 与 NSOperationQueue

NSOperation 需要和 NSOperationQueue 配合使用来实现多线程方案。单独使用 NSOperation 的话, 它是属于同步操作, 并不具备开启新线程的能力

NSOperation:操作

  • NSOperation 类是一个抽象类,不能直接使用它来封装任务,而是使用系统定义的子类( NSInvocationOperation 或 NSBlockOperation)或者自定义子类来封装任务。
  • 操作对象是一个单发对象,即它只执行一次任务,不能再次执行。通常通过将操作添加到操作队列来执行操作。

NSOperationQueue:队列

获取主队列:[NSOperationQueue mainQueue]
获取当前队列:[NSOperationQueue currentQueue]

NSOperation 使用

如果不想使用 NSOperationQueue,可以通过调用 NSOperation 对象的start方法来自己执行操作。默认情况下,调用 NSOperation 的 start 方法并不会开一条新线程去执行操作,而是在当前线程同步执行操作。

注意点:如果将操作添加到队列后,又调用 start 方法,会导致Crash

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
 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
[queue addOperation:operation];
[operation start];
}

- (void)test {
NSLog(@"%@",[NSThread currentThread]);
}

/*
2020-02-03 03:49:07.027372+0800 多线程[11489:1903781] <NSThread: 0x600003ee8140>{number = 3, name = (null)}
2020-02-03 03:49:07.031612+0800 多线程[11489:1903644] *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[NSInvocationOperation start]: something is trying to start the receiver simultaneously from more than one thread'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23b98bde __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff503b5b20 objc_exception_throw + 48
2 Foundation 0x00007fff25653930 -[NSOperation start] + 1424
......
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
*/
  • 模拟图片下载完成后回到主线程更新 UI:
1
2
3
4
5
6
7
8
9
10
11
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
NSLog(@"下载图片,%@",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"下载完成更新UI,%@",[NSThread currentThread]);
}];
}];
/*
2020-01-20 10:32:55.807874+0800 多线程[28682:6334587] 下载图片,<NSThread: 0x6000037dd480>{number = 5, name = (null)}
2020-01-20 10:32:55.808920+0800 多线程[28682:6334435] 下载完成更新UI,<NSThread: 0x6000037a6680>{number = 1, name = main}
*/

NSOperation进阶

什么是并发数?

并发数就是同时执行的任务数。
比如,同时开3个线程执行3个任务,并发数就是3。
但是,并发数是3,并不代表开启的线程数就是3,也有可能是4个或者5个。因为线程有可能在等待,进入了就绪状态。

@property NSInteger maxConcurrentOperationCount;

NSOperation 与 GCD 区别:

GCD 会自动重用线程,而 NSOperation 不会,会一直开线程。
而开太多线程反而会影响效率,我们需要自己控制,一般开 3-6 个。

队列的暂停/继续/取消操作

1
2
3
4
5
6
7
8
9
10
11
12
/*
NSOperationQueue
*/
// YES:暂停 / NO:继续(当前正在执行的操作会执行完毕,暂停后续的所有操作)
@property (getter=isSuspended) BOOL suspended;
// 取消队列中的所有操作(当前正在执行的操作会执行完毕,取消后续的所有操作)
- (void)cancelAllOperations;
/*
NSOperation
*/
// 也可以调用 NSOperation 的 cancel 方法移除单个操作
- (void)cancel;
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
- (IBAction)start:(id)sender {
self.queue = [[NSOperationQueue alloc]init];
self.queue.maxConcurrentOperationCount = 1;
NSLog(@"开始");
for (int i = 0; i < 10; i++) {
[self.queue addOperationWithBlock:^{
sleep(1);
NSLog(@"操作%d,%@",i,[NSThread currentThread]);
}];
}
}

- (IBAction)suspend:(id)sender {
self.queue.suspended = YES;
NSLog(@"暂停");
}

- (IBAction)resume:(id)sender {
self.queue.suspended = NO;
NSLog(@"继续");
}

- (IBAction)cancel:(id)sender {
[self.queue cancelAllOperations];
NSLog(@"取消");
}

/*
2020-01-20 19:40:36.128809+0800 多线程[29051:6389455] 开始
2020-01-20 19:40:37.133303+0800 多线程[29051:6389765] 操作0,<NSThread: 0x60000161cb80>{number = 8, name = (null)}
2020-01-20 19:40:38.138239+0800 多线程[29051:6520475] 操作1,<NSThread: 0x600001607440>{number = 9, name = (null)}
2020-01-20 19:40:39.142707+0800 多线程[29051:6389765] 操作2,<NSThread: 0x60000161cb80>{number = 8, name = (null)}
2020-01-20 19:40:39.804264+0800 多线程[29051:6389455] 暂停
2020-01-20 19:40:40.145399+0800 多线程[29051:6520475] 操作3,<NSThread: 0x600001607440>{number = 9, name = (null)}
2020-01-20 19:40:45.175465+0800 多线程[29051:6389455] 继续
2020-01-20 19:40:46.179761+0800 多线程[29051:6520475] 操作4,<NSThread: 0x600001607440>{number = 9, name = (null)}
2020-01-20 19:40:47.184917+0800 多线程[29051:6520585] 操作5,<NSThread: 0x6000016009c0>{number = 10, name = (null)}
2020-01-20 19:40:48.189422+0800 多线程[29051:6520585] 操作6,<NSThread: 0x6000016009c0>{number = 10, name = (null)}
2020-01-20 19:40:49.192921+0800 多线程[29051:6520475] 操作7,<NSThread: 0x600001607440>{number = 9, name = (null)}
2020-01-20 19:40:49.748142+0800 多线程[29051:6389455] 取消
2020-01-20 19:40:50.198083+0800 多线程[29051:6520585] 操作8,<NSThread: 0x6000016009c0>{number = 10, name = (null)}
*/

操作执行状态控制

操作的执行状态:

1
2
3
4
@property (readonly, getter=isReady) BOOL ready;         //就绪
@property (readonly, getter=isExecuting) BOOL executing; //正在执行
@property (readonly, getter=isFinished) BOOL finished; //完成
@property (readonly, getter=isCancelled) BOOL cancelled; //取消

怎么控制 NSOperation 的状态?

  • 如果只重写了main方法,底层控制变更操作执行完成状态,以及操作退出;

  • 如果重写了start方法,自行控制任务状态。

  • 系统是怎样移除一个isFinished = YES的 NSOperation 的?

    通过KVO。

参考

iOS - 多线程(四):NSOperation