浅谈NSOperation

就如同上一篇文章——《iOS多线程编程——浅谈GCD》中写到的,iOS的多线程开发中,最常用的就是NSThread,NSOperation,GCD,他们的抽象层次从左往右越来越高,这也就是越来越底层,性能也就越来越好。既然上次谈到了GCD,那么这次就不得不来谈谈他的兄弟NSOperation。

苹果官方文档是这样描述NSOperation的:

The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task. Because it is abstract, you do not use this class directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or NSBlockOperation) to perform the actual task. Despite being abstract, the base implementation of NSOperation does include significant logic to coordinate the safe execution of your task. The presence of this built-in logic allows you to focus on the actual implementation of your task, rather than on the glue code needed to ensure it works correctly with other system objects.

翻译:

NSOperation类是一个抽象类,通过这个类,你可以在一个任务将代码和数据联系起来。又因为他是抽象的,你不能直接使用它,而是使用它的子类(NSinvocationOperationNSBlockOperation)或者自定义一个子类来实现它实际上的任务。尽管他是抽象的,但是他最基本的方法包含了主要逻辑来保证线程安全。这使得你只要专心于你想要实现的逻辑处理,而不用关心用来处理连接的代码在这个工程中是否正确。

对于NSOperation,实际上他仅仅是一个抽象类。他的兄弟NSBlockOperationNSInvocationOperation才是我们日常编码过程中用到最多的两个Operation对象。
也就是说,如果真的要调用NSOperation对象的话,我们有3种方法:

  1. 使用NSInvocationOperation
  2. 使用NSBlockOperation
  3. 自己继承NSOperation,然后实现对应的内容

既然如此,我们就一个个来讲讲,让我们更加简单的了解NSOperation这个东西吧。

##NSInvocationOperation

###基本使用:

1
2
3
4
5
6
7
8
9
- (void)operationJustStart{
NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run:) object:@"hei"]; //1
[operation setCompletionBlock:^{ //2
NSLog(@"helloworld");
}];
[operation start]; //3
}

解释:

  1. 用来创建一个NSInvocationOperation对象,同时将执行时候的代码设置为run函数,并将参数中的object所指向的对象作为run函数的参数传入。
  2. 设置当该线程完成后,需要执行的代码块(Block)
  3. 线程开始。NSInvocationOperation中,如果直接使用start函数的话,那么就会直接在当前线程直接执行对应的代码(同步执行)

坑:

  1. 只能同步执行,如果需要使用异步,那么就需要调用NSOperationQueue。
  2. CompletionBlock的调用是在完成后一段时间后进行调用的,这个可能和runloop有关,这里先开个坑。一下是坑的代码:
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
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"1");
[self operationJustStart];
NSLog(@"2");
}
- (void)operationJustStart{
NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run:) object:@"hei"];
[operation setCompletionBlock:^{
NSLog(@"helloworld");
}];
[operation start];
}
- (void)run:(NSObject *)obj{
for (int i = 0 ; i < 10; i++) {
NSLog(@"%d %s",i,__func__);
}
}

调用的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
2016-07-18 13:09:11.344 NSOperation[59291:6646533] 1
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 0 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 1 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 2 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 3 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 4 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 5 -[ViewController run:]
2016-07-18 13:09:11.345 NSOperation[59291:6646533] 6 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 7 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 8 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 9 -[ViewController run:]
2016-07-18 13:09:11.346 NSOperation[59291:6646533] 2
2016-07-18 13:09:11.346 NSOperation[59291:6646590] helloworld

####注意!!!如果对一个NSOperation对象两次使用start方法,那么第二次调用将失败。因为此时operation已经被销毁。

使用NSOperationQueue进行异步调用

代码如下:

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)InvocationOperation{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"hello"];
[operation setCompletionBlock:^{
NSLog(@"setCompletionBlock");
}];
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"world"];
[operation2 setCompletionBlock:^{
NSLog(@"setCompletionBlock");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation];
[queue addOperation:operation2];
}
- (void)run:(NSObject *)obj{
for (int i = 0 ; i < 10; i++) {
NSLog(@"%d %s",i,__func__);
}
}

调用的结果如下:(很明显是异步调用,同时,是开了2个线程,毕竟2个operation嘛)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2016-07-18 13:14:56.145 NSOperation[59346:6655570] 1
2016-07-18 13:14:56.145 NSOperation[59346:6655570] 2
2016-07-18 13:14:56.145 NSOperation[59346:6655619] 0 -[ViewController run:]
2016-07-18 13:14:56.145 NSOperation[59346:6655609] 0 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 1 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 1 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 2 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 2 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 3 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 3 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 4 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 4 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 5 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655619] 6 -[ViewController run:]
2016-07-18 13:14:56.146 NSOperation[59346:6655609] 5 -[ViewController run:]
2016-07-18 13:14:56.147 NSOperation[59346:6655619] 7 -[ViewController run:]
2016-07-18 13:14:56.161 NSOperation[59346:6655619] 8 -[ViewController run:]
2016-07-18 13:14:56.161 NSOperation[59346:6655609] 6 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655619] 9 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655609] 7 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655609] 8 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655609] 9 -[ViewController run:]
2016-07-18 13:14:56.162 NSOperation[59346:6655633] setCompletionBlock
2016-07-18 13:14:56.162 NSOperation[59346:6655619] setCompletionBlock

##NSBlockOperation

这个方法根据名字就很容易得到他的概念,他就是可以通过多个block,来实现串行队列。

实现方法的话和前面这个类似,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)blockOperation{
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[self run];
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[self run];
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation1];
[queue addOperation:operation2];
}
- (void)run:(NSObject *)obj{
for (int i = 0 ; i < 10; i++) {
NSLog(@"%d %s",i,__func__);
}
}

执行结果如下,很明显是异步的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2016-07-18 19:35:05.637 NSOperation[64600:7323370] 1
2016-07-18 19:35:05.638 NSOperation[64600:7323370] 2
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 0 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 0 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 1 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 1 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 2 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 2 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 3 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 3 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323407] 4 -[ViewController run:]
2016-07-18 19:35:05.638 NSOperation[64600:7323405] 4 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323407] 5 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323405] 5 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323407] 6 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323405] 6 -[ViewController run:]
2016-07-18 19:35:05.639 NSOperation[64600:7323407] 7 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323407] 8 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323405] 7 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323407] 9 -[ViewController run:]
2016-07-18 19:35:05.640 NSOperation[64600:7323405] 8 -[ViewController run:]
2016-07-18 19:35:05.641 NSOperation[64600:7323405] 9 -[ViewController run:]

而在这里面有一个坑:

也就是如果你在创建block的时候调用了他的operation.name的话,就算你后面再进行设置,他的内容也不会发生改变。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)blockOperation{
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@ is complete 1",operation1.name);
}];
operation1.name = @"o1";
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@ is complete 2",operation1.name);
}];
operation2.name = @"o2";
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation1];
[queue addOperation:operation2];
}

结果如下:

1
2
3
4
2016-07-18 19:40:20.363 NSOperation[64660:7332789] 1
2016-07-18 19:40:20.364 NSOperation[64660:7332789] 2
2016-07-18 19:40:20.364 NSOperation[64660:7333007] (null) is complete 1
2016-07-18 19:40:20.364 NSOperation[64660:7332999] o1 is complete 2

##Custom Operation

最后再来讲讲自定义的Operation

由于要自定义,而且operation默认情况下是抽象类,所以我们对里面的部分内容必须要得到实现。

  1. 重写main方法
  2. 为了能够顺利的使用NSOperation中的cancel方法,我们需要在main方法中添加isCancel来判断是否被终止,从而确定能够在调用这个线程的时候,顺利终止这个线程。

代码如下:

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
- (void)main {
if (self.isCancelled) return;
// 获取图片数据
NSURL *url = [NSURL URLWithString:self.imageUrl];
NSData *imageData = [NSData dataWithContentsOfURL:url];
if (self.isCancelled) {
url = nil;
imageData = nil;
return;
}
// 初始化图片
UIImage *image = [UIImage imageWithData:imageData];
if (self.isCancelled) {
image = nil;
return;
}
if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {
// 把图片数据传回到主线程
[(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];
}
}

线程同步


在多线程中还有一个事情是十分重要的,那就是线程同步问题:

这个问题在GCD中可以通过dispatch_barrier_async来是的在那个时间段只能够使用一个代码块。这样就保证数据的安全性。

而在NSOperation,因为我们不需要关心到底