美文网首页iOS
NSOperation 系列知识总结

NSOperation 系列知识总结

作者: Chendy_Linda | 来源:发表于2019-04-09 16:44 被阅读0次

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

    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

    相关文章

      网友评论

        本文标题:NSOperation 系列知识总结

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