iOS 多线程之NSOperation

 

一:什么是NSOperation:

NSOperation ,是苹果提供的一套多线程解决方案。NSOperation是对GCD面向对象的封装。是完全面向对象的。

NSOperation是一个抽象类,并不能直接使用,没有封装操作的能力。想要封装操作,必须使用它的子类:

1:NSInvocationOperation

2:  NSBlockOperation

3: 继承自NSOperation封装的对象。实现内部相应的方法(程序员自定义)

 

二:NSOperation的使用以及使用场景

以上我们知道要使用NSOperation封装操作必须要使用它的子类来封装。

1 NSInvocationOperation。

初始化:initWithTarget:Select:Object

//:初始化方法1: initWithTarget:selector:object  在当前线程执行operation方法没有开启新的线程
    NSInvocationOperation * operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation) object:nil];
    [operation start];
-(void)operation
{
    NSLog(@"%@",[NSThread currentThread]);
}

另一个中初始化方法不常用,知道就可以了

NSMethodSignature * sign = [self.class instanceMethodSignatureForSelector:@selector(operationMethod)];
    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:sign];
    invocation.target = self;
    invocation.selector = @selector(operation);
    NSInvocationOperation * operaton2 = [[NSInvocationOperation alloc]initWithInvocation:invocation];
    [operaton2 start];

-(void)operation
{
    NSLog(@"%@",[NSThread currentThread]);
}

来看一下线程的情况: 

2019-03-14 11:12:23.225953+0800 MotherBaby[16847:1358173] {number = 1, name = main}
2019-03-14 11:12:23.226141+0800 MotherBaby[16847:1358173] {number = 1, name = main}

以上两种初始化方法,封装了一个NSInvocationOperation 对象,手动开启执行start方法之后,并没有开启新的线程,是在当前线程中完成的。

2 NSBlockOperation 

NSBlockOperation,是用一个block 封装操作。初始化方法如下:

 NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];

控制台打印情况:

2019-03-14 11:16:46.298272+0800 MotherBaby[16892:1361194] {number = 1, name = main}

可以看出,此时的NSBlockOperation 是在主线程即当前线程完成的。

如果只有一个操作,那么NSBlockOperation 是在当前线程完成的,如果多于一个操作,那么会开启新的线程来执行。向NSBlock Operation添加操作的方法如下:

  [blockOperation addExecutionBlock:^{
        NSLog(@"block");
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blockOperation start];

控制台打印情况:

2019-03-14 11:25:47.444035+0800 MotherBaby[17023:1367237] block
2019-03-14 11:25:47.444122+0800 MotherBaby[17023:1367214] {number = 1, name = main}
2019-03-14 11:25:47.444203+0800 MotherBaby[17023:1367237] {number = 3, name = (null)}

可以看出在添加了一个操作之后,就是多线程执行了。只得注意的是,在BlockOperation执行start操作之后是不可以执行addExecuttionBLock 操作的,程序会crash 。

NSBlockOperation是否开启新的线程取决于任务的个数,多于一个会多线程执行,并且这时候任务的执行在哪条线程是 由系统决定的。

3 自定义NSOperation

以上两种NSOperation 不能满足需求的时候,可以自定义NSOperation 对象。需要重写-(void)main 方法或者statr 方法。重写main 方法不需要管理当前任务的状态isExecution或者isFinished,只要main方法返回当前operation就结束了。

示例代码:


//MainOperaton
-(void)main
{
        if(!self.isCancelled)
        {
            NSLog(@"%@",[NSThread currentThread]);
        }
    
}


//调用这个自定义的Operatoon
  MainOperation * maino = [[MainOperation alloc]init];
    [maino start];
2019-03-14 12:00:38.337906+0800 MotherBaby[17857:1391101] {number = 1, name = main}
2019-03-14 12:00:38.338583+0800 MotherBaby[17857:1391101] {number = 1, name = main}

从打印情况可以看出,自定义的NSOperation子类并没有开启新的线程。

(关于自定义NSOperation 详细内容会在下一篇博文中探讨)

3 NSOperationQueue

NSOperationQueue分为两种,一种是主队列,凡是追加到主队列中的操作都会在主线程中执行(除了addExecutingBlock添加的操作,这个号方法添加的操作可能会在主线程之外的线程执行)。这一点跟GCD是相同的。另一种是普通自定义的NSOperationQueue 。自定义的NSOperation Queue 同时包含了串行和并发功能。

1 获取主队列。

 NSOperationQueue * mainQueue = [NSOperationQueue mainQueue];

2创建自定义NSOperationQueue

 NSOperationQueue * queue  = [[NSOperationQueue alloc]init];

添加到这种队列中的操作会自动派发到子线程中执行。同时包含了串行并发的功能。通过控制最大并发数来实现串行或者并发。

NSOperation要实现 多线程的功能需要添加到NSOperationQueue中(addExecutingWithBlock除外)。

添加方法1:需要创建好NSOperaton对象,然后调用addOperation:方法

  NSInvocationOperation * operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation2) object:nil];
    
    NSInvocationOperation * operation3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation3) object:nil];
   
    NSInvocationOperation * operation4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation4) object:nil];
     NSInvocationOperation * operation5 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation4) object:nil];
     NSInvocationOperation * operation6 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation4) object:nil];
     NSInvocationOperation * operation7 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation4) object:nil];
  
    NSOperationQueue * mainQueue = [[NSOperationQueue alloc]init];
    
    [mainQueue addOperation:operation2];
    [mainQueue addOperation:operation3];
    [mainQueue addOperation:operation4];
     [mainQueue addOperation:operation5];
     [mainQueue addOperation:operation6];
     [mainQueue addOperation:operation7];

控制台打印效果:

2019-03-14 12:36:10.196214+0800 MotherBaby[18333:1413306] {number = 6, name = (null)}
2019-03-14 12:36:10.196254+0800 MotherBaby[18333:1413307] {number = 5, name = (null)}
2019-03-14 12:36:10.196218+0800 MotherBaby[18333:1413305] {number = 3, name = (null)}
2019-03-14 12:36:10.196218+0800 MotherBaby[18333:1413294] {number = 4, name = (null)}
2019-03-14 12:36:10.196443+0800 MotherBaby[18333:1413306] {number = 6, name = (null)}
2019-03-14 12:36:10.196447+0800 MotherBaby[18333:1413305] {number = 3, name = (null)}

可以看出,添加进入NSOperationQueue 中的操作是在多个线程完成的,是并发执行。

添加方法2:addOperationWithBlock 不需要创建NSOperation 对象,直接把要执行的任务添加到NSOperationqueue中。

 NSOperationQueue * mainQueue = [[NSOperationQueue alloc]init];
    [mainQueue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];

控制台打印效果:

2019-03-14 12:39:45.189292+0800 MotherBaby[18376:1415476] {number = 5, name = (null)}
2019-03-14 12:39:45.189288+0800 MotherBaby[18376:1415480] {number = 4, name = (null)}
2019-03-14 12:39:45.189288+0800 MotherBaby[18376:1415479] {number = 6, name = (null)}
2019-03-14 12:39:45.189315+0800 MotherBaby[18376:1415481] {number = 3, name = (null)}
2019-03-14 12:39:45.189511+0800 MotherBaby[18376:1415480] {number = 4, name = (null)}

可以看出也是并发执行的。

4 NSOperationQueue 控制线程的串行与并发

NSOperatonqueue是通过属性maxConcurrentOperationCount来控制队列的串行与并发,控制的并不是线程数量,而是一个队列中可以并发执行的操作数,然后系统根据可以并发执行的操作数开启相对应合适数量的线程来执行操作。

maxConcurrentOperationCount : 默认是-1 ,此时可以并发执行;

                                                         =1 的时候,不可以并发执行,是串行的执行。

                                                         >1 时候是并发执行。

 通过这个属性开发者获得了控制操作最大并发数的权限,可以控制当前队列是串行还是并行,但是系统会根据当前系统资源状态自动调整并发数量。

示例代码:

NSOperationQueue * mainQueue = [[NSOperationQueue alloc]init];
    mainQueue.maxConcurrentOperationCount = 1;
    [mainQueue addOperationWithBlock:^{
        NSLog(@"任务1:%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
        NSLog(@"任务2:%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
       NSLog(@"任务3:%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
       NSLog(@"任务4:%@",[NSThread currentThread]);
    }];
    [mainQueue addOperationWithBlock:^{
        NSLog(@"任务5:%@",[NSThread currentThread]);
    }];
    
    mainQueue.maxConcurrentOperationCount = 1;
    
    

控制台打印情况:

2019-03-14 12:53:26.919149+0800 MotherBaby[18512:1422925] 任务1:{number = 3, name = (null)}
2019-03-14 12:53:26.919480+0800 MotherBaby[18512:1422923] 任务2:{number = 4, name = (null)}
2019-03-14 12:53:26.919633+0800 MotherBaby[18512:1422923] 任务3:{number = 4, name = (null)}
2019-03-14 12:53:26.919737+0800 MotherBaby[18512:1422923] 任务4:{number = 4, name = (null)}
2019-03-14 12:53:26.919833+0800 MotherBaby[18512:1422923] 任务5:{number = 4, name = (null)}

队列中的Operation 是串行执行的。可以看到他们并不是在同一个线程中执行的,从而说明,maxConcurrentOperationCount是控制的最大并发操作数而不是线程数数量,线程数量是由系统控制的。

maxConcurrentOperationCount =2 时候,最大并发Operation的数量就是2,同时执行的Operation最多为2,线程的数量还是由体统决定,我们并不需要关心。

5:NSOperation之间添加依赖关系

在NSOperationqueue中,如果想控制两个操作之间完成的顺序,可以为这两个操做添加依赖关系。

查看对象依赖的API:addDependency :添加依赖;

                                     removeDependency;移除依赖;

                                    @property (readonly, copy) NSArray *dependencies; 当前操作完成前已经完成的操作数组。

示例代码:

NSInvocationOperation * op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation2) object:nil];
    NSInvocationOperation * op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation3) object:nil];
    NSInvocationOperation * op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation4) object:nil];
   
    [op2 addDependency:op4];
  
    NSOperationQueue * mainQueue = [[NSOperationQueue alloc]init];
    [mainQueue addOperation:op2];
    [mainQueue addOperation:op3];
    [mainQueue addOperation:op4];
2019-03-14 13:18:31.503632+0800 MotherBaby[18781:1436982] (
    ""
)
2019-03-14 13:18:31.503683+0800 MotherBaby[18781:1437024] 任务3:{number = 3, name = (null)}
2019-03-14 13:18:31.503689+0800 MotherBaby[18781:1437017] 任务4:{number = 4, name = (null)}
2019-03-14 13:18:31.503914+0800 MotherBaby[18781:1437028] 任务2:{number = 5, name = (null)}

可以看到顺利的控制了Op2 跟Op4 之间的依赖关系。

6:NSOperation 的优先级

NSOperation提供了queuepriority(优先级属性),queuepriority属性使用与同一队列中的操作,不适用与不同队列的操作。

队列中的操作默认优先级都是NSOperationqueuePriorityNormal ,但是可以通过方法:setQueuePriority来设置操作的优先级。

操作的先后顺序取决于依赖关系。没有依赖关系的操作处于就绪状态,就绪状态的操作的执行顺序就取决于他们的优先级。

NSOperationQueuePriority 是 一个枚举,类型有:

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

操作在队列中的执行:

 就绪状态: 当前操作没有任何依赖操作还没有完成,这个操作就是执行就绪状态。等待执行。

例如,operation1 ,2,3,4添加到队列中,opreation2 依赖opreation1 ,opreation3依赖opreation4,但是1,4 并没有依赖关系,  所以1,4就是就绪状态  。

就绪状态的操作之间的执行顺序是决定于他们的优先级queuePrority,优先级不能取代依赖关系。

1 准备就绪状态的操作,执行顺序由优先级决定。

2,未准备就绪的操作,由依赖关系决定。

3 优先级高的未准备就绪,优先级低的准备就绪,还是会先执行优先级低的。

7线程之间的通信

线程之前的通信:

 [mainQueue addOperationWithBlock:^{
        NSLog(@"当前线程是:%@",[NSThread currentThread]);
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            NSLog(@"当前线程是:%@",[NSThread currentThread]);
        }];
    }];

     控制台打印:

2019-03-14 13:44:01.038620+0800 MotherBaby[51277:1547766] 当前线程是:{number = 3, name = (null)}
2019-03-14 13:44:01.049350+0800 MotherBaby[51277:1547729] 当前线程是:{number = 1, name = main}

这样可以回到主线程更新UI。

 

8 NSOperation 的常用方法

参考自https://www.jianshu.com/p/4b1d77054b35,感觉总结的很全面。

1 取消操作: -(void)cancel; 标记isCancelled状态。

2 判断操作状态:

- (BOOL)isFinished; 判断操作是否已经结束。

- (BOOL)isCancelled; 判断操作是否已经标记为取消。

- (BOOL)isExecuting; 判断操作是否正在在运行。

- (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。

其他一些方法:

操作同步


- (void)waitUntilFinished; 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。

- (void)setCompletionBlock:(void (^)(void))block; completionBlock 会在当前操作执行完毕时执行 completionBlock。

- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。

- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。

@property (readonly, copy) NSArray *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。



10.2 NSOperationQueue 常用属性和方法

取消/暂停/恢复操作


- (void)cancelAllOperations; 可以取消队列的所有操作。

- (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。

- (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。


操作同步


- (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。


添加/获取操作`


- (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象。

- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束

- (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。

- (NSUInteger)operationCount; 当前队列中的操作数。


获取队列


+ (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。

+ (id)mainQueue; 获取主队列。

 

 

 

 

 

 

你可能感兴趣的:(iOS多线程相关,NSOperation,iOS)