iOS NSOperation 非并发执行

NSOperation提供了一种面向对象的方法来封装任务。NSOperation可以单独执行,也可以放到NSOperationQueue中执行。
 
NSOperation是虚基类不能直接使用,但Cocoa提供了两个简单的子类NSBlockOperation和NSInvocationOperation。NSBlockOperation是将任务封装到block对象中,NSInvocationOperation 是将任务封装到selector。
 
NSBlockOperation 直接使用
- (void)startOperation
{
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation Test");
    }];
 
    [operation start];
}
 
调用start方法开始执行任务,NSOperation的实例方法cancel可以取消正在执行的任务,这比GCD有优势(GCD中不提供取消任务的功能)。但是这里并不是我们想像的这么简单调用一个cancel方法就够了,看下面代码:
- (void)startOperation
{
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        while (1) {
            NSLog(@"Test operation cancel funcation");
        }
    }];
   
    [operation start];
    [operation cancel];
}
 
start方法调用后while循环会一直执行,之后调用cancel方法,while循环会停止吗?答案是不会的,因为cancel方法就不会被执行,当前线程一直卡在block任务中。
你可能会想我在start方法前设置0.01s延迟后调用实例的cancel方法呢?看下面代码:
- (void)startOperation
{
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        while (1) {
            NSLog(@"Test operation cancel funcation");
        }
    }];
 
    self.operation = operation;
    [self performSelector:@selector(cancelOperation) withObject:nil afterDelay:0.01f];
 
    [operation start];
}
 
- (void)cancelOperation
{
    [self.operation cancel];
}
 
实际运行发现cancelOperation方法根本就不会调用,这是为什么呢?
NSOperation本身并不提供多线程的能力,任务是在当前线程中异步执行,任务执行完成后才执行后面的代码。cancel方法写在当前线程,而当前线程一直卡在while循环里,所以cancel方法根本就不会被调用。
 
既然在同一线程中不能取消死循环的任务,那么,将任务放到后台,在主线程中取消呢?NSOperationQueue提供多线程的能力,将NSOperation任务放到queue中执行。看下面代码:
- (void)startOperation
{
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        while (1) {
            NSLog(@"Test operation cancel funcation");
        }
    }];

    self.operation = operation;

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];
//    [operation cancel]; 
   
    [self performSelector:@selector(cancelOperation) withObject:nil afterDelay:0.01f];
}
 
这里的cancelOperation方法和上面一样省略了,operation任务被添加到NSOperationQueue中在下一个Runloop会被执行,如果紧跟添加后取消,任务就不会被执行。所以我们放到延迟方法中取消operation。这样总能退出while循环了吧!测试发现while循环依然在执行,这又是什么原因了?
 
在苹果官方文档上讲解NSOperation有这么一段话:
 If an operation were terminated outright, there might not be a way to reclaim resources that had been allocated. As a result, operation objects are expected to check for cancellation events and to exit gracefully when they occur in the middle of the operation.
大致意思是任务被中断了,但分配的内存资源有可能回收不了,所以在执行任务前要检查任务是否被取消了。还有保证任务被取消后释放分配的内存,这点在后面的实现NSOperation子类中要特别注意。
 
下面这样才是正确的
- (void)startOperation
{
    __weak ViewController *wself = self;
   
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        while (wself.operation.isCancelled == NO) {
            NSLog(@"Test operation cancel funcation");
        }
    }];

    self.operation = operation;

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];
 
    [self performSelector:@selector(cancelOperation) withObject:nil afterDelay:0.01f];
}
 
上面使用的都是系统提供的NSOperation的子类,我们也可以自己定义新的子类。NSOperation的任务默认是非并发执行的,只读属性 BOOL concurrent(iOS7之后使用 BOOL asynchronous)默认返回NO。
 
 
将NSOperationQueue之前先弄明白几个概念:线程、同步、异步、并发
线程是程序执行的最小单元,是进程中的一个实体,是被系统独立调度和分派的基本单位,同一进程中的多个线程可以并发执行。
线程同步是多个线程发生竞争资源,需要依次访问,线程异步是多个线程可以同时对同一资源进行访问
执行同步是等待任务完成才能执行后面的代码,执行异步是异步调用发出后,接着执行后面的代码,实际执行调用的过程在后面完成,像performSelector调用。
并发执行是指不需要等待任务执行完也能执行后面的代码。
 
上面我用到了NSOperationQueue,处于cocoa最上层处理多线程队列。NSOperationQueue会给加入的每个NSOperation任务开启一个新的线程,当任务执行完成后销毁其线程。多个任务是异步执行的,既为异步队列。但可以设置NSOperationQueue的最大同时执行的任务数为1(maxConcurrentOperationCount = 1)来实现同步队列。非并发的任务添加到NSOperationQueue队列中也实现了异步执行。因此如果你需要将NSOperation任务添加到NSOperationQueue队列中,那就不需要实现NSOperation的并发任务。
 
关于自定义NSOperation子类和实现concurrent任务会在下一节讲。
 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。