iOS-多线程编程学习之NSOperation(四)

  NSOperation是一个抽象类,可以用来封装一个任务的相关代码和数据。因为这是个抽象类,所以不能直接得到该类的实例化对象,而是需要继承该类实现其子类或者使用系统内置的两个子类(NSInvocationOperationNSBlockOperation)来执行实际的线程任务。

  一个Operation对象包含的任务只能执行一次。通常情况下只需要把Operation对象加入Operation queue(它是NSOperationQueue类的一个实例)即可。一个Operation queue可以直接执行Operation,或者开第二个线程运行Operation,甚至是间接的使用libdispatch库(类似GCD)。
  如果不使用Operation queue,也可以通过调用其start方法执行一个Operation。手动启动Operation会相对困难一点,因为一不小心启动一个不处于准备状态(ready trigger)的Operation将会导致异常发生。
  在这里我们不打算对NSOperation进行过多的说明。我们主要集中于介绍如何使用系统提供的NSInvocationOperationNSBlockOperationNSOperationQueue来进行多线程编程。

简单入门

使用Operation进行多线程编程的步骤很简单。

  • 实例化NSOperation子类实例,绑定要执行的操作。
  • 创建NSOperationQueue队列,并将NSOperation实例添加到队列中。

  然后NSOperationQueue队列将会自动检测队列中需要执行的Operation对象并执行它们。所有使用非常简单,还不需要对Operation的生命周期进行维护。

  在样例代码中首先创建了一个NSOperationQueue类型的属性,并且重写init方法对齐进行初始化操作。部分代码如下

@property (nonatomic, strong) NSOperationQueue *queue;

//重写init方法初始化该属性
-(instancetype)init
{
    self = [super init];
    if (self) {
        _queue = [[NSOperationQueue alloc] init];
    }
    return self;
}

NSInvocationOperation

  创建NSInvocationOperation主要有两个方法。

方法一

//方法一,创建执行特定方法的Operation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(selector) object:nil];
[self.queue addOperation:operation];

方法二

  对于NSInvocation,本质上是OC消息的一种静态描述,可以将其看成是把一个动作(action)封装到一个对象中。一个NSInvocation对象包含了OC消息的所有元素:目标(target),选择器(selector),参数(arguement),返回值(return value)。每个元素都是可以设置的,并且返回值会在齐被调度后自动设置相应的值。

//方法二,创建NSInvocation对象,并设置其相应参数,在用于创建NSInvocationOperation实例
NSInvocation *invocation =
    [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(run)]];
//设置selector属性
[invocation setSelector:@selector(run)];
//设置target属性
[invocation setTarget:self];
//创建实例
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithInvocation:invocation];
[self.queue addOperation:operation2];

NSBlockOperation

NSBlockOperation是NSOperation的一个子类之一。其创建方式比NSInvocationOperation看起来要更简便一点。

//创建实例
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 100; i++) {
        NSLog(@"%zd ===> %@", i, [NSThread currentThread]);
    }
}];
//添加到队列中
[self.queue addOperation:operation];

自定义NSOperation子类

  自定义NSOperation子类和自定义NSThread子类的方式非常的像,都是继承父类,重写它们的main方法执行特定的任务。

LYSOperation.h

#import <Foundation/Foundation.h>

@interface LYSOperation : NSOperation
@end

LYSOperation.m

/**
 *  重写main方法,里面是Operation被调度时执行的任务
 */
-(void)main
{
    NSLog(@"这里是自定义的NSOperation子类LYSOperation的实现.");
}

然后我们想使用别的NSOperation子类一样,只要将其加入NSOperationQueue队列中即可。

LYSOperation *operation = [[LYSOperation alloc] init];
[self.queue addOperation:operation];

operation间依赖关系

  NSOperation还有一个特性就是可以创建Operation之间的依赖关系。就是如果A依赖于B,则A需要在B执行完之后才会执行。这样我们就可以很方便拥有特定执行顺序的任务集了。但是需要注意的是要避免创建依赖循环!

NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"以来测试 ===> 任务A");
}];

NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"以来测试 ===> 任务B");
}];

NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"以来测试 ===> 任务C");
}];

//设置A依赖于B,B依赖于C
[operationA addDependency:operationB];
[operationB addDependency:operationC];

//添加Operation到队列中
[self.queue addOperations:@[operationA, operationB, operationC] waitUntilFinished:NO];

  现在我们来看一下结果。首先我们看一下不添加依赖时候的结果。我们会发现三个任务之间多次执行之后可能会出现任何的执行顺序。

2016-02-23 21:50:43.255 ThreadDemon[7245:589407] 依赖测试 ===> 任务A
2016-02-23 21:50:43.255 ThreadDemon[7245:589410] 依赖测试 ===> 任务C
2016-02-23 21:50:43.255 ThreadDemon[7245:589409] 依赖测试 ===> 任务B

我们再来看看添加了以来之后的执行结果。我们会发现无论运行多少次肯定都是先执行C,再执行B,最后执行A任务,这样依赖关系就可以得到体现了。

2016-02-23 21:52:59.482 ThreadDemon[7261:591072] 依赖测试 ===> 任务C
2016-02-23 21:52:59.483 ThreadDemon[7261:591070] 依赖测试 ===> 任务B
2016-02-23 21:52:59.483 ThreadDemon[7261:591070] 依赖测试 ===> 任务A

NSOperationQueue

这里不对NSOperation进行过多的深入介绍,主要是简单介绍几个常用的方法。

设置最大并发线程数

  NSOperationQueue提供了方法设置最大可以并发执行的Operation数量。

[_queue setMaxConcurrentOperationCount:5];

获取当前执行Operation的Queue

NSOperationQueue *queue = [NSOperationQueue currentQueue];

获取绑定到主线程上的NSOperationQueue对象

NSOperationQueue *queue = [NSOperationQueue mainQueue];

Demon源代码

这里给出Github项目上的源代码。
传送门:Github-iOS_Framework_study-ThreadDemon

参考资料

  1. 苹果官方文档-NSOperation
  2. 苹果官方文档-NSInvocationOperation
  3. 苹果官方文档-NSBlockOperation
  4. 苹果官方文档-NSOperationQueue
  5. 苹果官方文档-NSInvocation
  6. iOSNSOperationQueue的使用NSInvocation用来呈现objective-C
  7. 多线程编程3 - NSOperationQueue