NSOperation 系列知识总结

基本总结文章很多,看烦了不好意思!

image.png

1. 基本使用

NSOperation 本身是个抽象类,具体使用需要用到它的2个子类:NSInvocationOperation 和 NSBlockOperation

1.1 NSInvocationOperation

Invocation 调用的意思,NSInvocationOperation大概就是操作调用,最一种最基本的方式。我们使用- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg; 来初始化一个NSInvocationOperation 实例,然后通过这个实例调用- (void)start;方法来执行一个操作。

/** 在当前线程直接使用 就相当于直接调用方法,没开启新线程 **/
    NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocation) object:nil];
    [invocationOp start];

- (void)testInvocation{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_A:%@",[NSThread currentThread]);
    }
}

//输出结果
2018-12-13 15:03:32.498648+0800 NSOperation学习[20787:370581] Thread_A:<NSThread: 0x6000008e9400>{number = 1, name = main}
2018-12-13 15:03:32.498913+0800 NSOperation学习[20787:370581] Thread_A:<NSThread: 0x6000008e9400>{number = 1, name = main}

结果可以看出,直接创建一个NSInvocationOperation对象,通过start()执行一个任务,就相当于直接调用方法,没开启新线程。当前在哪个线程调用start(),那么任务就在那个线程执行。

1.2 NSBlockOperation

1.2.1 基本使用 -(void)blockOperationWithBlock:^{}

有个Block,意思就是可以通过Block直接传入执行的任务。

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

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

//输出
2018-12-13 15:11:12.111775+0800 NSOperation学习[21036:376084] BlockOperationThread:<NSThread: 0x600003205380>{number = 1, name = main}
2018-12-13 15:11:12.112124+0800 NSOperation学习[21036:376084] BlockOperationThread:<NSThread: 0x600003205380>{number = 1, name = main}

可以看出和上面NSInvocationOperation直接调用一样,直接在当前线程直接使用 就相当于直接调用方法,没开启新线程。

1.2.2 追加任务 - (void)addExecutionBlock:(void (^)(void))block;
 NSBlockOperation *addBlockOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"addBlockOp原始任务:%@",[NSThread currentThread]);
    }];
    [addBlockOp addExecutionBlock:^{
        NSLog(@"addBlockOp追加任务1:%@",[NSThread currentThread]);
    }];
    [addBlockOp addExecutionBlock:^{
        NSLog(@"addBlockOp追加任务2:%@",[NSThread currentThread]);
    }];
    [addBlockOp addExecutionBlock:^{
        NSLog(@"addBlockOp追加任务3:%@",[NSThread currentThread]);
    }];
    [addBlockOp addExecutionBlock:^{
        NSLog(@"addBlockOp追加任务4:%@",[NSThread currentThread]);
    }];
    [addBlockOp start];

//输出
2018-12-13 15:16:47.419463+0800 NSOperation学习[21217:379510] addBlockOp追加任务3:<NSThread: 0x600003bfca40>{number = 4, name = (null)}
2018-12-13 15:16:47.419456+0800 NSOperation学习[21217:379511] addBlockOp追加任务1:<NSThread: 0x600003bc3640>{number = 3, name = (null)}
2018-12-13 15:16:47.419456+0800 NSOperation学习[21217:379513] addBlockOp原始任务:<NSThread: 0x600003bfca00>{number = 5, name = (null)}
2018-12-13 15:16:47.419463+0800 NSOperation学习[21217:379457] addBlockOp追加任务2:<NSThread: 0x600003b94c00>{number = 1, name = main}
2018-12-13 15:16:47.419693+0800 NSOperation学习[21217:379511] addBlockOp追加任务4:<NSThread: 0x600003bc3640>{number = 3, name = (null)}

如果使用 addExecutionBlock追加任务后,那么所有的任务执行在那个线程中都是系统决定的,包括最原始的blockOperationWithBlock中的任,具体在哪个线程,开辟多少线程都是系统决定的。

2. 队列 NSOperationQueue

队列功能理解起来,就是把多个任务组织起来干事情。至于怎么干,先干那个后干那个,那就看这个队列是什么队列了。

它分为两种 :NSOperationQueue *main = [NSOperationQueue mainQueue]主队列,主队列都是在主线程中执行的,是串行的。 和 NSOperationQueue *diyQuene = [[NSOperationQueue alloc]init] 自定义队列。,自定义队列在子线程中执行的,可以通过一些参数来控制它是串行 还是 并行的!

我们一般都是申请一些列任务NSInvocationOperationNSBlockOperation 扔到一个队列中执行。也可以直接扔一些block给队列执行。

2.1 [NSOperationQueue mainQueue] 主队列

NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocation) object:nil];
NSInvocationOperation *invocationOp1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationB) object:nil];
NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationC) object:nil];
NSBlockOperation *blockOp1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockA:%@",[NSThread currentThread]);
            sleep(2);
        }
    }];
[blockOp1 addExecutionBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockB:%@",[NSThread currentThread]);
            sleep(2);
        }
}];
[blockOp1 addExecutionBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockC:%@",[NSThread currentThread]);
            sleep(2);
        }
}];

NSOperationQueue *main = [NSOperationQueue mainQueue];
[main addOperation:invocationOp];
[main addOperation:invocationOp1];
[main addOperation:blockOp1];
[main addOperation:invocationOp2];

- (void)testInvocation{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_A:%@",[NSThread currentThread]);
    }
}

- (void)testInvocationB{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_B:%@",[NSThread currentThread]);
    }
}

- (void)testInvocationC{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_C:%@",[NSThread currentThread]);
    }
}
//输出
2018-12-13 17:08:31.704728+0800 NSOperation学习[24205:435940] Thread_A:<NSThread: 0x600002155380>{number = 1, name = main}
2018-12-13 17:08:31.704924+0800 NSOperation学习[24205:435940] Thread_A:<NSThread: 0x600002155380>{number = 1, name = main}
2018-12-13 17:08:31.706094+0800 NSOperation学习[24205:435940] Thread_B:<NSThread: 0x600002155380>{number = 1, name = main}
2018-12-13 17:08:31.706249+0800 NSOperation学习[24205:435940] Thread_B:<NSThread: 0x600002155380>{number = 1, name = main}
2018-12-13 17:08:31.707041+0800 NSOperation学习[24205:435940] Thread_BlockB:<NSThread: 0x600002155380>{number = 1, name = main}
2018-12-13 17:08:31.707067+0800 NSOperation学习[24205:435993] Thread_BlockA:<NSThread: 0x600002139700>{number = 3, name = (null)}
2018-12-13 17:08:31.707098+0800 NSOperation学习[24205:435992] Thread_BlockC:<NSThread: 0x60000213a0c0>{number = 4, name = (null)}
2018-12-13 17:08:31.707528+0800 NSOperation学习[24205:435940] Thread_BlockB:<NSThread: 0x600002155380>{number = 1, name = main}
2018-12-13 17:08:31.707478+0800 NSOperation学习[24205:435993] Thread_BlockA:<NSThread: 0x600002139700>{number = 3, name = (null)}
2018-12-13 17:08:31.707588+0800 NSOperation学习[24205:435992] Thread_BlockC:<NSThread: 0x60000213a0c0>{number = 4, name = (null)}
2018-12-13 17:08:31.708544+0800 NSOperation学习[24205:435940] Thread_C:<NSThread: 0x600002155380>{number = 1, name = main}
2018-12-13 17:08:31.708905+0800 NSOperation学习[24205:435940] Thread_C:<NSThread: 0x600002155380>{number = 1, name = main}

可以看出,主队列对于Operation都是顺序执行的,这里面执行的整体顺序就是添加任务的顺序 invocationOp -->invocationOp1-->blockOp1-->invocationOp2注意我说的整体来看。这里blockOp1的任务通过addExecutionBlock它具体在哪个线程执行变得不可控,但是整体所有任务来看invocationOp2任务还是在blockOp1任务执行完毕后再执行。

2.2 自定义队列 [[NSOperationQueue alloc]init]

NSOperationQueue *diyQuene = [[NSOperationQueue alloc]init];
NSInvocationOperation *invocationOp1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationB) object:nil];
NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationC) object:nil];
NSBlockOperation *blockOp1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockA:%@",[NSThread currentThread]);
            sleep(2);
        }
    }];
    [blockOp1 addExecutionBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockB:%@",[NSThread currentThread]);
            sleep(2);
        }
        
    }];
    [blockOp1 addExecutionBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockC:%@",[NSThread currentThread]);
            sleep(2);
        }
    }];

    [diyQuene addOperation:invocationOp1];
    [diyQuene addOperation:invocationOp2];
    [diyQuene addOperation:blockOp1];

//输出
2018-12-13 17:41:30.080302+0800 NSOperation学习[25161:456997] Thread_BlockB:<NSThread: 0x6000033dfb00>{number = 6, name = (null)}
2018-12-13 17:41:30.080304+0800 NSOperation学习[25161:456995] Thread_BlockA:<NSThread: 0x6000033dfac0>{number = 5, name = (null)}
2018-12-13 17:41:30.080302+0800 NSOperation学习[25161:456999] Thread_C:<NSThread: 0x6000033e0800>{number = 3, name = (null)}
2018-12-13 17:41:30.080330+0800 NSOperation学习[25161:456994] Thread_B:<NSThread: 0x6000033e0600>{number = 4, name = (null)}
2018-12-13 17:41:30.080562+0800 NSOperation学习[25161:456994] Thread_B:<NSThread: 0x6000033e0600>{number = 4, name = (null)}
2018-12-13 17:41:30.080562+0800 NSOperation学习[25161:456999] Thread_C:<NSThread: 0x6000033e0800>{number = 3, name = (null)}
2018-12-13 17:41:30.080606+0800 NSOperation学习[25161:456996] Thread_BlockC:<NSThread: 0x6000033dfe80>{number = 7, name = (null)}
2018-12-13 17:41:32.083185+0800 NSOperation学习[25161:456995] Thread_BlockA:<NSThread: 0x6000033dfac0>{number = 5, name = (null)}
2018-12-13 17:41:32.083185+0800 NSOperation学习[25161:456997] Thread_BlockB:<NSThread: 0x6000033dfb00>{number = 6, name = (null)}
2018-12-13 17:41:32.083253+0800 NSOperation学习[25161:456996] Thread_BlockC:<NSThread: 0x6000033dfe80>{number = 7, name = (null)}

自定义队列添加的任务都会放在子线程中去执行 而且是并发执行的。注意全部在子线程中,不会有例外。

2.3 队列也可以 不通过addOperation: 添加任务执行任务,而直接通过addOperationWithBlock:添加Block任务

NSOperationQueue *diyQuene = [[NSOperationQueue alloc]init];
    [diyQuene addOperationWithBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockA:%@",[NSThread currentThread]);
        }
    }];
    [diyQuene addOperationWithBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockB:%@",[NSThread currentThread]);
        }
    }];
    [diyQuene addOperationWithBlock:^{
        for (int index = 0; index < 2; index++){
            NSLog(@"Thread_BlockC:%@",[NSThread currentThread]);
        }
    }];
    [diyQuene addOperationWithBlock:^{
        for (int index = 0; index < 2; index++) {
            NSLog(@"Thread_BlockD:%@",[NSThread currentThread]);
        }
    }];

//输出
2018-12-13 17:44:52.601516+0800 NSOperation学习[25256:458882] Thread_BlockB:<NSThread: 0x600002bb8b40>{number = 3, name = (null)}
2018-12-13 17:44:52.601523+0800 NSOperation学习[25256:458883] Thread_BlockA:<NSThread: 0x600002b87640>{number = 4, name = (null)}
2018-12-13 17:44:52.601740+0800 NSOperation学习[25256:458882] Thread_BlockB:<NSThread: 0x600002bb8b40>{number = 3, name = (null)}
2018-12-13 17:44:52.601769+0800 NSOperation学习[25256:458883] Thread_BlockA:<NSThread: 0x600002b87640>{number = 4, name = (null)}
2018-12-13 17:44:52.601937+0800 NSOperation学习[25256:458881] Thread_BlockC:<NSThread: 0x600002bb8a80>{number = 5, name = (null)}
2018-12-13 17:44:52.601941+0800 NSOperation学习[25256:458880] Thread_BlockD:<NSThread: 0x600002bb8ac0>{number = 6, name = (null)}
2018-12-13 17:44:52.602127+0800 NSOperation学习[25256:458881] Thread_BlockC:<NSThread: 0x600002bb8a80>{number = 5, name = (null)}
2018-12-13 17:44:52.602141+0800 NSOperation学习[25256:458880] Thread_BlockD:<NSThread: 0x600002bb8ac0>{number = 6, name = (null)}

执行的方式和通过addOperation:一样。这里是自定义队列,可以看出都是并发执行的。

2.4 自定义队列的串行,并行

    NSOperationQueue *diyQuene = [[NSOperationQueue alloc]init];
    diyQuene.maxConcurrentOperationCount = 1;
    
    NSInvocationOperation *invocationOp1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationB) object:nil];
    NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationC) object:nil];
    NSInvocationOperation *invocationOp3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationD) object:nil];
    
    [diyQuene addOperation:invocationOp1];
    [diyQuene addOperation:invocationOp2];
    [diyQuene addOperation:invocationOp3];

- (void)testInvocationB{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_B:%@",[NSThread currentThread]);
        sleep(2);
    }
}

- (void)testInvocationC{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_C:%@",[NSThread currentThread]);
        sleep(2);
    }
}

- (void)testInvocationD{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_D:%@",[NSThread currentThread]);
        sleep(2);
    }
}

maxConcurrentOperationCount = 1 输出:

2018-12-17 11:11:07.236201+0800 NSOperation学习[2549:52701] Thread_B:<NSThread: 0x600001740a40>{number = 3, name = (null)}

2018-12-17 11:11:09.240357+0800 NSOperation学习[2549:52701] Thread_B:<NSThread: 0x600001740a40>{number = 3, name = (null)}

2018-12-17 11:11:11.245801+0800 NSOperation学习[2549:52703] Thread_C:<NSThread: 0x600001746d00>{number = 4, name = (null)}

2018-12-17 11:11:13.250472+0800 NSOperation学习[2549:52703] Thread_C:<NSThread: 0x600001746d00>{number = 4, name = (null)}

2018-12-17 11:11:15.255169+0800 NSOperation学习[2549:52701] Thread_D:<NSThread: 0x600001740a40>{number = 3, name = (null)}

2018-12-17 11:11:17.256041+0800 NSOperation学习[2549:52701] Thread_D:<NSThread: 0x600001740a40>{number = 3, name = (null)}

任务是一个一个执行的,就相当于串行。

maxConcurrentOperationCount = 2 输出:

2018-12-17 14:08:53.708174+0800 NSOperation学习[5240:119410] Thread_C:<NSThread: 0x600000cc5f40>{number = 4, name = (null)}
2018-12-17 14:08:53.708174+0800 NSOperation学习[5240:119411] Thread_B:<NSThread: 0x600000cc5ec0>{number = 3, name = (null)}

2018-12-17 14:08:55.713523+0800 NSOperation学习[5240:119411] Thread_B:<NSThread: 0x600000cc5ec0>{number = 3, name = (null)}
2018-12-17 14:08:55.713538+0800 NSOperation学习[5240:119410] Thread_C:<NSThread: 0x600000cc5f40>{number = 4, name = (null)}

2018-12-17 14:08:57.719642+0800 NSOperation学习[5240:119413] Thread_D:<NSThread: 0x600000ccc940>{number = 5, name = (null)}

2018-12-17 14:08:59.723658+0800 NSOperation学习[5240:119413] Thread_D:<NSThread: 0x600000ccc940>{number = 5, name = (null)}

任务是2个一组并发执行。

maxConcurrentOperationCount = -1 输出:

2018-12-17 14:11:51.504204+0800 NSOperation学习[5287:120741] Thread_D:<NSThread: 0x60000376acc0>{number = 4, name = (null)}
2018-12-17 14:11:51.504226+0800 NSOperation学习[5287:120740] Thread_B:<NSThread: 0x60000376ad80>{number = 5, name = (null)}
2018-12-17 14:11:51.504230+0800 NSOperation学习[5287:120742] Thread_C:<NSThread: 0x60000376ac80>{number = 3, name = (null)}

2018-12-17 14:11:53.509613+0800 NSOperation学习[5287:120741] Thread_D:<NSThread: 0x60000376acc0>{number = 4, name = (null)}
2018-12-17 14:11:53.509623+0800 NSOperation学习[5287:120742] Thread_C:<NSThread: 0x60000376ac80>{number = 3, name = (null)}
2018-12-17 14:11:53.509638+0800 NSOperation学习[5287:120740] Thread_B:<NSThread: 0x60000376ad80>{number = 5, name = (null)}

任务是3个并发执行的。

总结:控制自定义队列的串 并发执行方式。maxConcurrentOperationCount = 1 的时候就变成了串行,maxConcurrentOperationCount > 1 的情况下,变成了指并发队列,但是执行的最大操作数由maxConcurrentOperationCount决定。当然肯定有个极限值,是系统决定的。如果maxConcurrentOperationCount < 0,最大操作数,任务并发执行,就完全交给系统控制了。

这里maxConcurrentOperationCount控制的并不是最大线程数,而是任务数,具体操作在哪个线程中进行,那是系统分配的问题。可能有点绕,但这就是NSOperation的魅力所在,理解它的时候我们要摒弃GCD的那种,毕竟人家是基于GCD的高级封装。在使用时更多的是关注具体的操作执行?执行了几个操作?是同时执行还是并发执行?而不是关注操作在哪个线程中执行的。

在上面的例子中也侧面印证的在NSOperation中,同一个操作任务都是在同一个线程中执行

3. NSOperation,NSOperationQueue 依赖(Dependency)

方法:

- (void)addDependency:(NSOperation *)op; //添加
- (void)removeDependency:(NSOperation *)op; //移除
@property (readonly, copy) NSArray<NSOperation *> *dependencies; //依赖不止一个
    NSOperationQueue *diyQuene = [[NSOperationQueue alloc]init];

    NSInvocationOperation *invocationOp1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationB) object:nil];
    NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationC) object:nil];
    NSInvocationOperation *invocationOp3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationD) object:nil];

    [invocationOp1 addDependency:invocationOp2];
    [invocationOp2 addDependency:invocationOp3];

    [diyQuene addOperation:invocationOp1];
    [diyQuene addOperation:invocationOp2];
    [diyQuene addOperation:invocationOp3];

- (void)testInvocationB{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_B:%@",[NSThread currentThread]);
        sleep(2);
    }
}

- (void)testInvocationC{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_C:%@",[NSThread currentThread]);
        sleep(2);
    }
}

- (void)testInvocationD{
    for (int index = 0; index < 2; index++){
        NSLog(@"Thread_D:%@",[NSThread currentThread]);
        sleep(2);
    }
}

输出

2018-12-17 17:35:38.787592+0800 NSOperation学习[8642:212491] Thread_D:<NSThread: 0x600002a45700>{number = 3, name = (null)}
2018-12-17 17:35:40.789662+0800 NSOperation学习[8642:212491] Thread_D:<NSThread: 0x600002a45700>{number = 3, name = (null)}
2018-12-17 17:35:42.794605+0800 NSOperation学习[8642:212492] Thread_C:<NSThread: 0x600002a44e80>{number = 4, name = (null)}
2018-12-17 17:35:44.796616+0800 NSOperation学习[8642:212492] Thread_C:<NSThread: 0x600002a44e80>{number = 4, name = (null)}
2018-12-17 17:35:46.799939+0800 NSOperation学习[8642:212491] Thread_B:<NSThread: 0x600002a45700>{number = 3, name = (null)}
2018-12-17 17:35:48.801465+0800 NSOperation学习[8642:212491] Thread_B:<NSThread: 0x600002a45700>{number = 3, name = (null)}

看出,如果让invocationOp1依赖 invocationOp2invocationOp2依赖 invocationOp3,打印结果会发现任务D完全执行完毕才执行任务C任务C完全执行完成才执行任务B

这样可以看成一个串行的队列了,只是这个串行的队列执行任务不是在主线程中实现的,而是在子线程中实现的,而且这个串行队列执行多个操作的顺序是可控的。如果用上面的maxConcurrentOperationCount = 1来实现,达到同样效果,需要在添加顺序上 ''动手脚'':

    NSOperationQueue *diyQuene = [[NSOperationQueue alloc]init];
    diyQuene.maxConcurrentOperationCount = 1;

    NSInvocationOperation *invocationOp1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationB) object:nil];
    NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationC) object:nil];
    NSInvocationOperation *invocationOp3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationD) object:nil];

    //倒着添加顺序
    [diyQuene addOperation:invocationOp3];
    [diyQuene addOperation:invocationOp2];
    [diyQuene addOperation:invocationOp1];

4. NSOperation queuePriority优先级

通过NSOperation的属性queuePriority可以给同一个队列里面的不同操作加一个控制优先级的属性,控制不同操作的执行顺序。 优先级默认有以下这些类型,默认为NSOperationQueuePriorityNormal

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

实际测试,发现对NSOperation 并不能控制任务的执行完成的顺序,记住我说的是完成的顺训,也就是说他不能控制操作任务的依赖关系(就是通过设置NSOperationQueuePriority来决定那个任务先完成),它控制的只是任务是否先开始(准备就绪状态)。下面例子是很好的说明:

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

    NSInvocationOperation *invocationOp1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationB) object:nil];
    [invocationOp1 setQueuePriority:NSOperationQueuePriorityLow];

    NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationC) object:nil];
    [invocationOp2 setQueuePriority:NSOperationQueuePriorityNormal];

    NSInvocationOperation *invocationOp3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testInvocationD) object:nil];
    [invocationOp3 setQueuePriority:NSOperationQueuePriorityVeryHigh];

    [diyQuene addOperation:invocationOp1];
    [diyQuene addOperation:invocationOp2];
    [diyQuene addOperation:invocationOp3];

输出结果1:

2018-12-18 14:00:38.280316+0800 NSOperation学习[16597:372370] Thread_C:<NSThread: 0x600002e33000>{number = 3, name = (null)}
2018-12-18 14:00:38.280323+0800 NSOperation学习[16597:372369] Thread_B:<NSThread: 0x600002e0d080>{number = 5, name = (null)}
2018-12-18 14:00:38.280382+0800 NSOperation学习[16597:372371] Thread_D:<NSThread: 0x600002e330c0>{number = 4, name = (null)}
2018-12-18 14:00:40.285559+0800 NSOperation学习[16597:372370] Thread_C:<NSThread: 0x600002e33000>{number = 3, name = (null)}
2018-12-18 14:00:40.285578+0800 NSOperation学习[16597:372369] Thread_B:<NSThread: 0x600002e0d080>{number = 5, name = (null)}
2018-12-18 14:00:40.285610+0800 NSOperation学习[16597:372371] Thread_D:<NSThread: 0x600002e330c0>{number = 4, name = (null)}

输出结果2:

2018-12-18 14:02:20.566624+0800 NSOperation学习[16635:373508] Thread_B:<NSThread: 0x600002751b00>{number = 5, name = (null)}
2018-12-18 14:02:20.566627+0800 NSOperation学习[16635:373510] Thread_C:<NSThread: 0x600002751a40>{number = 4, name = (null)}
2018-12-18 14:02:20.566623+0800 NSOperation学习[16635:373513] Thread_D:<NSThread: 0x600002762880>{number = 3, name = (null)}
2018-12-18 14:02:22.570790+0800 NSOperation学习[16635:373510] Thread_C:<NSThread: 0x600002751a40>{number = 4, name = (null)}
2018-12-18 14:02:22.570813+0800 NSOperation学习[16635:373508] Thread_B:<NSThread: 0x600002751b00>{number = 5, name = (null)}
2018-12-18 14:02:22.570790+0800 NSOperation学习[16635:373513] Thread_D:<NSThread: 0x600002762880>{number = 3, name = (null)}

并没有按照我们设想的那样:D-C-B 的顺训执行任务。如果需要设置操作的执行顺序,官方建议我们还是使用依赖:addDependency:

下面翻译官方的解释:

这个属性是设置某个操作的优先级,这个值通常会影响队列中操作的执行或移除顺序,具体取值参考NSOperationQueuePriority。如果属性未设置,默认为NSOperationQueuePriorityNormal

使用这个优先级设置仅仅作为一系列没有相互依赖操作执行时的优先级分类。优先级设置不能用于操作队列的依赖管理。如果你需要设置不同操作的依赖,需要使用addDependency:方法。

5. NSOperation qualityOfService 调取系统资源优先级

通过NSOperation的属性qualityOfService 可以设置这个操作在系统层面利用系统资源的优先级。

typedef NS_ENUM(NSInteger, NSQualityOfService) {

    //用户交互级别的,如Event,UI
    NSQualityOfServiceUserInteractive = 0x21, 
  
    //待交互层面,如初始化UI,加载UI,后续动作就是NSQualityOfServiceUserInteractive级别的。
    //For example, loading an email after a user has selected it in a message list.
    NSQualityOfServiceUserInitiated = 0x19, 

    //一些不需要立即出现结果的操作,一些可能是用户请求或自动初始化的任务。如:定期内容更新,批量文件操作,媒体导入
    NSQualityOfServiceUtility = 0x11,
   
    //一般不是用户直接关系的操作,不需要立即响应,后台默默进行的,
    //如:预抓取内容,搜索索引、备份、或与外部系统的数据同步。
    NSQualityOfServiceBackground = 0x09,

    //表明你没有指定这个操作服务优先级,一般需要设置一个具体的服务优先级
    //如果不设置,系统会在NSQualityOfServiceUserInteractive 和 NSQualityOfServiceUtility 选一个
    NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

设置这个属性,也和设置queuePriority一样,不能控制操作的执行顺序,他的官方文档解释:

服务优先级决定了操作利用系统资源(如:CPU,网络资源,硬盘资源等)优先级。服务优先级越高,系统给予的硬件资源就越高,这样可能导致操作执行的越快。你可以使用这个来确保用户要求的明确的任务更优先明确的执行。

默认情况下一个操作的服务优先级是:NSQualityOfServiceBackground。一般情况下你需要使用默认值,当改变服务级别,使用最低水平,适合执行相应的任务。举个例子,如果用户初始化了一个任务并且等待着它完成,把这个任务服务优先级设置成NSQualityOfServiceUserInteractive,系统在资源允许的条件下会给予更多的资源来执行这个操作。

其实这关键是NSQualityOfService每个值代表的是什么意义,根据自己操作任务类型的需求选择合适的NSQualityOfService

6. NSOperation 的线程通信

和GCD类似,解决的是在子线程中回到主线程的问题。

// 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 异步进行耗时操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }

        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 进行一些 UI 刷新等操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
    }];
出处。

7. NSOperation 的线程安全

既然NSOperation 可以异步执行任务,必然设计到线程安全的问题。
摘抄:

线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。

作者:行走少年郎
链接:https://www.jianshu.com/p/4b1d77054b35
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

还是回到GCD 买票的例子中

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

    NSInvocationOperation *invocationOp1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    NSInvocationOperation *invocationOp2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    
    [diyQuene addOperation:invocationOp1];
    [diyQuene addOperation:invocationOp2];

-(void)saleTicket{
    
    while (1) {
        
//        [_lock lock];
        
        if (_ticketCount == 0){
            NSLog(@"票买完了");
            break;
        }else{
            
            _ticketCount -= 1;
            
            NSLog(@"剩余票数:%ld Thread:%@",(long)_ticketCount,[NSThread currentThread]);
            sleep(0.5);
        }
        
//        [_lock unlock];
    }
}

//输出
2018-12-19 17:01:53.222921+0800 NSOperation学习[30872:714469] 剩余票数:48 Thread:<NSThread: 0x600003fcc8c0>{number = 4, name = (null)}
2018-12-19 17:01:53.222949+0800 NSOperation学习[30872:714467] 剩余票数:49 Thread:<NSThread: 0x600003fff600>{number = 3, name = (null)}
2018-12-19 17:01:53.223210+0800 NSOperation学习[30872:714469] 剩余票数:47 Thread:<NSThread: 0x600003fcc8c0>{number = 4, name = (null)}
2018-12-19 17:01:53.223248+0800 NSOperation学习[30872:714467] 剩余票数:47 Thread:<NSThread: 0x600003fff600>{number = 3, name = (null)}
2018-12-19 17:01:53.223332+0800 NSOperation学习[30872:714469] 剩余票数:46 Thread:<NSThread: 0x600003fcc8c0>{number = 4, name = (null)}
2018-12-19 17:01:53.223373+0800 NSOperation学习[30872:714467] 剩余票数:45 Thread:<NSThread: 0x600003fff600>{number = 3, name = (null)}
2018-12-19 17:01:53.223429+0800 NSOperation学习[30872:714469] 剩余票数:44 Thread:<NSThread: 0x600003fcc8c0>{number = 4, name = (null)}
2018-12-19 17:01:53.223470+0800 NSOperation学习[30872:714467] 剩余票数:43 Thread:<NSThread: 0x600003fff600>{number = 3, name = (null)}
2018-12-19 17:01:53.223538+0800 NSOperation学习[30872:714469] 剩余票数:42 Thread:<NSThread: 0x600003fcc8c0>{number = 4, name = (null)}
2018-12-19 17:01:53.224522+0800 NSOperation学习[30872:714469] 剩余票数:41 Thread:<NSThread: 0x600003fcc8c0>{number = 4, name = (null)}

看结果输出47 的时候出现问题,发现剩余票数出现混乱,这就是多线程同时访问一个资源导致,GCD也有这个例子。如果没有异常可以多运行几次看看,因为设计的多线程同时访问公共资源,都是有概率出问题。

使用加锁控制,这里使用NSLock,当然也可以使用dispatch_semaphore。打开屏蔽代码就可以控制这个问题!

-(void)saleTicket{
    
    while (1) {
        
        [_lock lock];
        
        if (_ticketCount == 0){
            NSLog(@"票买完了");
            break;
        }else{
            
            _ticketCount -= 1;
            
            NSLog(@"剩余票数:%ld Thread:%@",(long)_ticketCount,[NSThread currentThread]);
            sleep(0.5);
        }
        
        [_lock unlock];
    }
}

8. NSOperation 使用案例

参考

  1. https://www.jianshu.com/p/4b1d77054b35
  2. 苹果开发者文档Developer Documnetation
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 拆书帮《终身成长》五 I片段还原 今天讲述了如何去培养自己的成长型思维模式,作者分了四点来讲述 1接受,你要接受你...
    一米之粒阅读 387评论 0 0
  • 第一,请自爱自重,不要为了潇洒忽视了别有用心的殷勤。 第二,外出要跟家人、朋友保持联系,最好不要深夜出行。 第三,...
    池鱼思故渊吖阅读 133评论 0 1
  • 妈妈早上给我交代,九点要到万达广场去排练,我迷迷糊糊的答应一了声,好的。九点半吧,准时送我出发,而且一路上...
    舒庆春阅读 269评论 0 1
  • 读书有益,凤凰花开。 无记录不生活。 专注情绪与感性情感的修行。 信仰这件事,不是基督耶稣,不是上帝。 遇到一个陌...
    天天向上的树阅读 394评论 0 2