美文网首页
iOS-多线程(五)NSOperation NSOperatio

iOS-多线程(五)NSOperation NSOperatio

作者: 厦门_小灰灰 | 来源:发表于2019-07-22 17:20 被阅读0次

NSOperation类是iOS2.0推出的,从OS X10.6和iOS4推出GCD时,又重写了NSOperation和NSOperationQueue,NSOperation和NSOperationQueue分别对应GCD的任务和队列,NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。比 GCD 更加灵活,功能也更加强大。

1. Operation Queues(NSOperation,NSOperationQueue) vs. Grand Central Dispatch (GCD)

  • Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;

  • GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手;

2. NSOperation,NSOperationQueue

  • NSOperation就是对任务进行的封装。它本身是一个抽象类,不能直接实例化。如果我们想要使用它来执行具体任务,就必须创建自己的子类(继承NSOperation)或者直接使用系统提供的两个子类:NSInvocationOperation 和 NSBlockOperation;

  • NSOperationQueue就是任务的执行队列,可进行串行队列的执行或并发队列的执行;

3. NSOperation开启的两种方式

  • 第一种:直接由NSOperation子类对象启动。 首先将需要执行的操作封装到NSOperation子类对象中,然后该对象调用start方法。直接使用start方法,那么操作会在当前线程中同步执行,不会创建新线程。

  • 第二种:NSOperation对象添加到NSOperationQueue对象中,由该队列对象启动操作,操作会异步执行:
    (1)创建操作:先将需要执行的操作封装到一个 NSOperation 对象中;
    (2)创建队列:创建 NSOperationQueue 对象;
    (3)将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中;
    系统自动从队列中取出线程,并且自动放到线程中执行。

4. NSOperation和NSOperationQueue的基本使用

4.1 NSInvocationOperation简单使用
  • 不通过NSOperationQueue
//NSInvocationOperation  同步执行
- (void)invocationOperationSync
{
    
    //创建 operation
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任务1"];
    
    [operation start];
    
}

- (void)task:(NSString *)taskName {
    for (NSInteger i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"%@---%@", taskName, [NSThread currentThread]);
    }
}

打印结果:

2019-07-20 20:18:15.101039+0800 NSOperationDemo[4388:112069] 任务1---<NSThread: 0x6000026d9380>{number = 1, name = main}
2019-07-20 20:18:16.102574+0800 NSOperationDemo[4388:112069] 任务1---<NSThread: 0x6000026d9380>{number = 1, name = main}

在没有使用 NSOperationQueue,在主线程中单独使用使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。

  • 通过NSOperationQueue
//NSInvocationOperation  异步执行
- (void)invocationOperationAsync
{
    //创建 operation
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"任务1"];
    //创建一个操作队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //将任务添加到队列中
    [queue addOperation:operation];
}

打印结果:

2019-07-20 20:52:31.394786+0800 NSOperationDemo[4838:122958] 任务1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}
2019-07-20 20:52:32.396118+0800 NSOperationDemo[4838:122958] 任务1---<NSThread: 0x60000321dcc0>{number = 3, name = (null)}

使用 NSOperationQueue,操作会异步并发执行,会开辟新线程。

4.2 NSBlockOperation简单使用
  • 不通过NSOperationQueue
//NSBlockOperation 同步执行
- (void)blockOperationSync
{
    //创建 operation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"block operation---%@", [NSThread currentThread]);
        }
    }];
    
    [operation start];
}

打印结果:

2019-07-20 21:48:56.673690+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}
2019-07-20 21:48:57.675189+0800 NSOperationDemo[5561:142065] block operation---<NSThread: 0x60000018e980>{number = 1, name = main}

同样在没有使用 NSOperationQueue,不会开启新的线程。

  • 通过NSOperationQueue
//NSBlockOperation 异步执行
- (void)blockOperationAsync
{
    //创建 operation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"block operation---%@", [NSThread currentThread]);
        }
    }];
    
    //创建一个操作队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //将任务添加到队列中
    [queue addOperation:operation];
}

打印结果:

2019-07-20 21:54:58.094872+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}
2019-07-20 21:54:59.099257+0800 NSOperationDemo[5650:144283] block operation---<NSThread: 0x600002ab1540>{number = 3, name = (null)}

使用 NSOperationQueue,操作会异步并发执行,会开辟新线程。

另外,NSBlockOperation 还提供了一个方法。

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

这个方法可以为 NSBlockOperation 添加额外的操作。每个操作可以异步并发执行,所有相关的操作执行完成时,整个操作才算完成。
简单示例:

//NSBlockOperation addExecutionBlock
- (void)blockOperationAddExecutionBlock
{
    //创建 operation
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务1:---%@", [NSThread currentThread]);
        }
    }];
    
    for (NSInteger i = 2; i < 7; ++i) {
        [operation addExecutionBlock:^{
            for (NSInteger j = 0; j < 2; ++j) {
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"任务%ld:---%@", i, [NSThread currentThread]);
            }
        }];
    }
    
    [operation start];
}

打印结果:

2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55710] 任务4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55757] 任务1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:51.309602+0800 NSOperationDemo[2028:55755] 任务2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:51.309611+0800 NSOperationDemo[2028:55758] 任务3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310394+0800 NSOperationDemo[2028:55757] 任务1:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:52.310441+0800 NSOperationDemo[2028:55758] 任务3:---<NSThread: 0x600000e71400>{number = 4, name = (null)}
2019-07-22 10:43:52.310392+0800 NSOperationDemo[2028:55710] 任务4:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:52.310452+0800 NSOperationDemo[2028:55755] 任务2:---<NSThread: 0x600000e748c0>{number = 5, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55757] 任务5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}
2019-07-22 10:43:53.311787+0800 NSOperationDemo[2028:55710] 任务6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55710] 任务6:---<NSThread: 0x600000e1c180>{number = 1, name = main}
2019-07-22 10:43:54.313078+0800 NSOperationDemo[2028:55757] 任务5:---<NSThread: 0x600000e4b680>{number = 3, name = (null)}

从打印结果可以看出,任务1的操作没有在主线程执行,任务4的操作在主线程执行,得出:

添加的操作多的话,blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行,这是由系统决定的,并不是说添加到 blockOperationWithBlock: 中的操作一定会在当前线程中执行。同样addExecutionBlock: 也是一样,在哪个线程执行是系统决定。

4.3 自定义NSOperation

当系统预定义的两个子类 NSInvocationOperation 和 NSBlockOperation 不能很好的满足我们的需求时,我们可以自定义自己的 NSOperation 子类,添加我们想要的功能。

可以通过重写 main 或者 start 方法 来定义自己的 NSOperation 对象。重写main方法比较简单,我们不需要管理操作的状态属性 isExecuting 和 isFinished。当 main 执行完返回的时候,操作就结束(isFinished为YES)。

简单示例:

//NonConcurrentOperation.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NonConcurrentOperation : NSOperation

@end

NS_ASSUME_NONNULL_END

//NonConcurrentOperation.m
#import "NonConcurrentOperation.h"

@implementation NonConcurrentOperation

- (void)main
{
    for (NSInteger i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任务,---%@", [NSThread currentThread]);
    }
}

@end

使用NonConcurrentOperation类

- (void)nonConcurrentOperationAction
{
    //不加入队列
    NonConcurrentOperation *operation = [[NonConcurrentOperation alloc] init];
    [operation start];
    
    NSLog(@"---------");
    
    //加入队列
    NonConcurrentOperation *operation1 = [[NonConcurrentOperation alloc] init];
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:operation1];
}

打印结果:

2019-07-22 15:02:22.924678+0800 NSOperationDemo[4590:129265] 任务,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926285+0800 NSOperationDemo[4590:129265] 任务,---<NSThread: 0x600000eda900>{number = 1, name = main}
2019-07-22 15:02:23.926549+0800 NSOperationDemo[4590:129265] ---------
2019-07-22 15:02:24.932274+0800 NSOperationDemo[4590:129326] 任务,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}
2019-07-22 15:02:25.934638+0800 NSOperationDemo[4590:129326] 任务,---<NSThread: 0x600000eb6380>{number = 3, name = (null)}

在默认情况下,operation 是同步执行的,也就是说在调用它的 start 方法的线程中执行它们的任务。而在 operation 和 operation queue 结合使用时,operation queue 可以为非并发的 operation 提供线程,因此,大部分的 operation 仍然可以异步执行。

如果你想要手动地执行一个 operation ,又想这个 operation 能够异步执行的话,你需要做一些额外的配置来让你的 operation 支持并发执行。

下面列举了一些你可能需要重写的方法:

  • start :必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现;

  • main :可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰;

  • isExecuting 和 isFinished :必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化;

  • isConcurrent :必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES 。

注意:当一个 operation 开始执行后,它会一直执行它的任务直到完成或被取消为止。我们可以在任意时间点取消一个 operation ,甚至是在它还未开始执行之前。为了让我们自定义的 operation 能够支持取消事件,我们需要在代码中定期地检查 isCancelled 方法的返回值,一旦检查到这个方法返回 YES ,我们就需要立即停止执行接下来的任务。根据苹果官方的说法,isCancelled 方法本身是足够轻量的,所以就算是频繁地调用它也不会给系统带来太大的负担。

简单示例:

//MyOperation.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MyOperation : NSOperation



@end

NS_ASSUME_NONNULL_END

//MyOperation.m
#import "MyOperation.h"

@interface MyOperation()

@property (nonatomic, assign) BOOL executing;
@property (nonatomic, assign) BOOL finished;

@end

@implementation MyOperation

@synthesize executing = _executing;
@synthesize finished = _finished;

#pragma mark - start
- (void)start
{
    NSLog(@"任务开始");
    //开始执行
    self.executing = YES;
    
    //执行循环操作
    for (NSInteger i = 0; i < 10; ++i) {
        
        if ( self.cancelled ) {
            //如果任务已经被取消了,更改状态后,返回
            self.executing = NO;
            self.finished = YES;
            NSLog(@"任务取消,退出");
            return;
        }
        
        NSLog(@"Task %ld, thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", (long)i, [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
        [NSThread sleepForTimeInterval:0.2];
    }
    
    NSLog(@"任务结束");
    self.executing = NO;
    self.finished = YES;
    
    NSLog(@"end...thread:%@,executing=%d,cancelled=%d,finished=%d,operationCount=%lu", [NSThread currentThread], self.executing, self.cancelled, self.finished, (unsigned long)[[NSOperationQueue currentQueue] operationCount]);
    
}

//任务是否是并发
- (BOOL)isAsynchronous
{
    return YES;
}

#pragma mark - getter & setter

- (void)setExecuting:(BOOL)executing
{
    [self willChangeValueForKey:@"executing"];
    _executing = executing;
    [self didChangeValueForKey:@"executing"];
}

- (BOOL)executing
{
    return _executing;
}

- (void)setFinished:(BOOL)finished
{
    [self willChangeValueForKey:@"finished"];
    _finished = finished;
    [self didChangeValueForKey:@"finished"];
}

@end

使用MyOperation类:

- (void)customOperationAction
{
    MyOperation *operation = [[MyOperation alloc] init];
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:operation];
}

打印结果:

2019-07-22 15:28:09.950993+0800 NSOperationDemo[5038:140737] 任务开始
2019-07-22 15:28:09.951745+0800 NSOperationDemo[5038:140737] Task 0, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.153603+0800 NSOperationDemo[5038:140737] Task 1, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.354902+0800 NSOperationDemo[5038:140737] Task 2, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.557117+0800 NSOperationDemo[5038:140737] Task 3, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.758551+0800 NSOperationDemo[5038:140737] Task 4, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:10.960494+0800 NSOperationDemo[5038:140737] Task 5, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.163521+0800 NSOperationDemo[5038:140737] Task 6, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.363928+0800 NSOperationDemo[5038:140737] Task 7, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.566943+0800 NSOperationDemo[5038:140737] Task 8, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.767499+0800 NSOperationDemo[5038:140737] Task 9, thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=1,cancelled=0,finished=0,operationCount=1
2019-07-22 15:28:11.969353+0800 NSOperationDemo[5038:140737] 任务结束
2019-07-22 15:28:11.969781+0800 NSOperationDemo[5038:140737] end...thread:<NSThread: 0x600002fb6b80>{number = 3, name = (null)},executing=0,cancelled=0,finished=1,operationCount=1

即使一个 operation 是被 cancel 掉了,我们仍然需要手动触发 isFinished 的 KVO 通知。因为当一个 operation 依赖其他 operation 时,它会观察所有其他 operation 的 isFinished 的值的变化,只有当它依赖的所有 operation 的 isFinished 的值为 YES 时,这个 operation 才能够开始执行。因此,如果一个我们自定义的 operation 被取消了但却没有手动触发 isFinished 的 KVO 通知的话,那么所有依赖它的 operation 都不会执行。

4.4 NSOperationQueue 控制串行执行、并发执行

操作队列中有个关键属性 maxConcurrentOperationCount,叫做最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行。
maxConcurrentOperationCount的值为:

  • -1,默认情况就是-1,表示不进行限制,可进行并发执行;
  • 0,不会有任何操作执行;
  • 1,队列为串行队列,只能串行执行;
  • 大于1,队列为并发队列,这个值也不是无限大,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值};

可以通过

@property (class, readonly, strong) NSOperationQueue *mainQueue API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

获取到主队列。

简单示例:

- (void)testMacConcurrentOperationCount
{
    //创建 operation1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务1:---%@", [NSThread currentThread]);
        }
    }];
    
    //创建 operation2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务2:---%@", [NSThread currentThread]);
        }
    }];
    
    //创建 operation3
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务3:---%@", [NSThread currentThread]);
        }
    }];
    
    //创建 operation4
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务4:---%@", [NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [NSOperationQueue new];
//    queue.maxConcurrentOperationCount = 0;
//    queue.maxConcurrentOperationCount = 1;
//    queue.maxConcurrentOperationCount = 2;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
}

最大并发操作数为0 打印结果:

没有任何输出。

最大并发操作数为1 打印结果:

2019-07-22 15:44:09.151837+0800 NSOperationDemo[5356:150014] 任务1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:10.155735+0800 NSOperationDemo[5356:150014] 任务1:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:11.161370+0800 NSOperationDemo[5356:150012] 任务2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:12.167015+0800 NSOperationDemo[5356:150012] 任务2:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:13.172285+0800 NSOperationDemo[5356:150014] 任务3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:14.172991+0800 NSOperationDemo[5356:150014] 任务3:---<NSThread: 0x600002d5a340>{number = 3, name = (null)}
2019-07-22 15:44:15.175897+0800 NSOperationDemo[5356:150012] 任务4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}
2019-07-22 15:44:16.181339+0800 NSOperationDemo[5356:150012] 任务4:---<NSThread: 0x600002d596c0>{number = 4, name = (null)}

最大并发操作数为2 打印结果:

2019-07-22 15:45:02.487935+0800 NSOperationDemo[5377:150560] 任务1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:02.487965+0800 NSOperationDemo[5377:150558] 任务2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488880+0800 NSOperationDemo[5377:150558] 任务2:---<NSThread: 0x600000428740>{number = 3, name = (null)}
2019-07-22 15:45:03.488888+0800 NSOperationDemo[5377:150560] 任务1:---<NSThread: 0x60000040f0c0>{number = 4, name = (null)}
2019-07-22 15:45:04.494115+0800 NSOperationDemo[5377:150561] 任务4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:04.494168+0800 NSOperationDemo[5377:150559] 任务3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150561] 任务4:---<NSThread: 0x60000040e940>{number = 5, name = (null)}
2019-07-22 15:45:05.498621+0800 NSOperationDemo[5377:150559] 任务3:---<NSThread: 0x60000040eb80>{number = 6, name = (null)}

当最大并发操作数为0时,没有任何操作执行;
当最大并发操作数为1时,操作是按顺序串行执行的;
当最大操作并发数为2时,操作是并发执行的,可以同时执行两个操作;

5. 操作之间的依赖

通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。
配置 operation 的依赖关系主要涉及到 NSOperation 类中的以下两个方法:

//添加依赖,使当前操作依赖于操作 op 的完成
- (void)addDependency:(NSOperation *)op;
//移除依赖,取消当前操作对操作 op 的依赖
- (void)removeDependency:(NSOperation *)op;

简单示例:

- (void)testDependency
{
    //创建 operation1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务1:---%@", [NSThread currentThread]);
        }
    }];
    
    //创建 operation2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务2:---%@", [NSThread currentThread]);
        }
    }];
    
    [operation1 addDependency:operation2];
    
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
}

打印结果:

2019-07-22 16:29:06.178541+0800 NSOperationDemo[5934:164383] 任务2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:07.184051+0800 NSOperationDemo[5934:164383] 任务2:---<NSThread: 0x600000d8dbc0>{number = 3, name = (null)}
2019-07-22 16:29:08.189099+0800 NSOperationDemo[5934:164386] 任务1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}
2019-07-22 16:29:09.194669+0800 NSOperationDemo[5934:164386] 任务1:---<NSThread: 0x600000de8380>{number = 4, name = (null)}

注意:
- 不要造成循环依赖,不然会出现等待的情况,导致操作都不发执行;
- 另外添加依赖要在队列添加操作之前,因为队列添加操作后,什么时候执行是系统决定的,有可能执行会比添加依赖还更快;

6. NSOperation 队列中的优先级

对于被添加到 操作队列 中的 操作 来说,决定它们执行顺序的第一要素是它们的 isReady 状态,其次是它们在队列中的优先级。操作 的 isReady 状态取决于它的依赖关系,而在队列中的优先级则是 操作本身的属性。默认情况下,所有新创建的操作在队列中的优先级都是 NSOperationQueuePriorityNormal 的,但是我们可以根据需要通过改变queuePriority属性值

@property NSOperationQueuePriority queuePriority;

来提高或降低 operation 的队列优先级。

优先级的取值有:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

需要注意的是,队列优先级只应用于相同操作队列中的操作之间,不同操作队列中的操作不受此影响。

另外,队列优先级和依赖关系之间的关系:

  • 操作的队列优先级只决定当前所有 isReady 状态为 YES 的操作的执行顺序。比如,在一个操作队列中,有一个高优先级和一个低优先级的操作 ,并且它们的 isReady 状态都为 YES ,那么高优先级的 operation 将会优先执行。而如果这个高优先级的操作的 isReady 状态为 NO ,而低优先级的操作的 isReady 状态为 YES 的话,那么这个低优先级的操作反而会优先执行;
  • 操作的队列中有一个准备就绪状态的操作和一个未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。但是准备就绪的操作依赖于未准备就绪操作的完成。虽然准备就绪的操作优先级低,也会优先执行。优先级不能取代依赖关系。如果要控制操作间的执行顺序,则必须使用依赖关系;

7. 线程之间的通信

场景:耗时操作,比如下载操作会放在子线程中处理,当子线程结束后,需要回到主线程中设置刷新UI。

简单示例:

- (void)threadCommunication
{
    //1 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //2 创建操作
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        
        //2.1 执行耗时操作
        NSURL *url = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1902385933,516700697&fm=26&gp=0.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        NSLog(@"图片下载完毕-----%@",[NSThread currentThread]);
        
        //2.2 获取主队列,回到主线程执行block里面的任务
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            //在主线程中,刷新UI
            self.imageView.image = image;
        }];
    }];
    
    //3 加入队列
    [queue addOperation:operation];
}

8. 暂停和恢复操作队列

如果我们想要暂停和恢复执行操作队列中的操作 ,可以通过调用 操作队列的 setSuspended: 方法来实现这个目的。不过需要注意的是,暂停执行操作队列并不能使正在执行的操作暂停执行,而只是简单地暂停调度新的操作。另外,我们并不能单独地暂停执行一个操作,除非直接 cancel 掉。

暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

参考资料:
iOS 并发编程之 Operation Queues

Demo传送门

over!

相关文章

网友评论

      本文标题:iOS-多线程(五)NSOperation NSOperatio

      本文链接:https://www.haomeiwen.com/subject/ilculctx.html