免费开源的iOS开发学习平台

NSOperation:1-基本使用

NSOperation在iOS4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。NSOperation是一个抽象类,因此系统为我们提供了NSBlockOperation和NSInvocationOperation两个子类,并且可以创建继承自NSOperation的自定义类。相比于GCD,NSOperation更加面向对象,开发者除了不需要去了解线程相关的概念之外,甚至连GCD中需要了解的异步/同步、并行/串行都不太需要深入了解,开发者只要懂得任务和队列即可。

NSOperation的类族

由于NSOperation是一个抽象类,因此不能够直接使用NSOperation,但苹果提供了两个NSOperation的子类,NSBlockOperation和NSInvocationOperation,除此之外,我们还可以使用自定义CustomOperation类。

对于NSBlockOperation类来讲,可以把任务封装在一个block块之内。NSBlockOperation类提供了如下操作方法。

  • 创建一个NSBlockOperation对象,任务封装在block中
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

对于NSInvocationOperation类来说,需要执行的任务直接指定已定义的方法。

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;

无队列情况下执行任务

在不创建队列的情况下,可以直接调用NSOperation类的start方法来执行某个任务。该任务是在当前线程进行执行的,如果是在主线程调用的start方法,那么则会在主线程中执行任务。

下方的示例代码中,在当前线程中串行执行两个任务。

/*
 调用operation的start方法,在当前线程中串行执行,无队列
 */
-(void) executeInCurrentThread {
    NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task1-----%@", [NSThread currentThread]);
    }]; 
    NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread currentThread]);        
    }]; 
    //调用start方法,会在当前线程中串行执行
    [task1 start];
    [task2 start];    
}

运行结果如下。

在队列中执行任务

当把任务放到队列(NSOperationQueue类)中进行执行时,系统会自动并行执行所有任务,对于开多少条线程之类的事务,完全交由系统处理,开发者只要把任务添加到队列中即可。另外,可以通过设置队列的maxConcurrentOperationCount属性,来设置并行执行任务的数量。

下方的示例代码中,在一个队列中插入了5个任务,这5个任务并行执行。系统会同时开启多个线程,多个任务并行执行。另外,maxConcurrentOperationCount决定了“并发”任务的数量,而不是创建线程的数量。即便设置为1,不同的任务也有可能在不同的线程中执行

-(void) executeInQueue {
    NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task1-----%@", [NSThread currentThread]);
    }];    
    NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread currentThread]);       
    }];   
    NSBlockOperation *task3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task3-----%@", [NSThread currentThread]);        
    }];    
    NSBlockOperation *task4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task4-----%@", [NSThread currentThread]);        
    }];
        NSBlockOperation *task5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task5-----%@", [NSThread currentThread]);      
    }];
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //设置队列属性
    queue.maxConcurrentOperationCount = 5;   
    //添加任务到队列
    [queue addOperation:task1];
    [queue addOperation:task2];
    [queue addOperation:task3];
    [queue addOperation:task4];
    [queue addOperation:task5];
}

运行结果如下。可见系统同时执行5个任务,执行的顺序是不确定的,并且创建了5条线程。

在任务中添加新任务

通过调用NSOperation类的addExecutionBlock方法,可以为某个NSOperation对象增加额外的任务。当把这些新增的任务放到队列中执行时,也是并行执行的。

- (void)addExecutionBlock:(void (^)(void))block; 

下方的示例代码中在任务中添加新任务,所有的任务都会并行执行。

-(void) addTaskInOperation {
    NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task1-----%@", [NSThread currentThread]);
    }];   
    NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread currentThread]);     
    }];    
    //task1中添加task
    [task1 addExecutionBlock:^{
        NSLog(@"task1 add task-----%@", [NSThread currentThread]);
    }];    
    //task2中添加task
    [task2 addExecutionBlock:^{
        NSLog(@"task2 add task-----%@", [NSThread currentThread]);
    }];    
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];    
    //添加任务到队列
    [queue addOperation:task1];
    [queue addOperation:task2]; 
}

运行结果如下。

在队列中直接添加任务

通过调用NSOperationQueue的addOperationWithBlock方法,可以向队列中直接添加任务block块。

-(void) addTaskInQueue {   
    NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task1-----%@", [NSThread currentThread]);
    }];  
    NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread currentThread]);        
    }];    
    //task1中添加task
    [task1 addExecutionBlock:^{
        NSLog(@"task1 add task-----%@", [NSThread currentThread]);
    }];   
    //task2中添加task
    [task2 addExecutionBlock:^{
        NSLog(@"task2 add task-----%@", [NSThread currentThread]);
    }];    
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];    
    //添加任务到队列
    [queue addOperation:task1];
    [queue addOperation:task2];   
    //在queue中添加任务
    [queue addOperationWithBlock:^{
        NSLog(@"queue task-----%@", [NSThread currentThread]);
    }];    
}

运行结果如下。向队列queue中直接添加任务,也是并行执行的。

在任务中创建completionBlock

在NSOperation类中,可以通过设置completionBlock来创建所有任务执行完成后,自动调用的一个block块。该block的执行是在任务执行后被调用的,有先后顺序

/*
 可以给任务Operation结束后添加block,但是block中的任务会在新的线程中执行,即:仅仅是添加了一个任务执行的先后顺序关系
 */
-(void) addCompletionBlock {
    NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task1-----%@", [NSThread currentThread]);
    }];  
    NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2-----%@", [NSThread currentThread]);       
    }];   
    //task1中添加task
    [task1 addExecutionBlock:^{
        NSLog(@"task1 add task-----%@", [NSThread currentThread]);
    }];    
    task1.completionBlock = ^{
        NSLog(@"task1 end!!! %@", [NSThread currentThread]);
    };    
    //task2中添加task
    [task2 addExecutionBlock:^{
        NSLog(@"task2 add task-----%@", [NSThread currentThread]);
    }];    
    task2.completionBlock = ^{
        NSLog(@"task2 end!!! %@", [NSThread currentThread]);
    };    
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];    
    //添加任务到队列
    [queue addOperation:task1];
    [queue addOperation:task2];
} 

运行结果如下。

NSOperation与GCD的区别

NSOperation与GCD都是苹果提供的用于多线程操作的技术,但是在使用过程中也稍有区别。

  • GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objective-C的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而NSOperation作为一个对象,为我们提供了更多的选择;

  • 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);

  • NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;

  • 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样能比GCD更加有效地掌控我们执行的后台任务;

  • 在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;

  • 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

示例代码

https://github.com/99ios/11.3.1