美文网首页
iOS多线程 - NSOperation详解

iOS多线程 - NSOperation详解

作者: 凉秋落尘 | 来源:发表于2019-04-26 18:00 被阅读0次

    前言

    本来想对NSOperation进行比较详细的说明,但是刚好看到一篇文章,介绍的比较好这里我就不再去介绍NSOperation,对于NSOperation的介绍可以参考:

    iOS 多线程:『NSOperation、NSOperationQueue』详尽总结

    这边我通过一些例子和实战演练来介绍NSOperation:

    介绍和优点

    NSOperation是一个抽象类,不能直接使用它,而是使用系统定义的子类NSInvocationOperationNSBlockOperation执行实际任务。尽管是抽象的,但基本实现确实包含了协调安全执行任务的重要逻辑。这种内置逻辑的存在使您可以专注于任务的实际实现,而不是确保它与其他系统对象正常工作所需的粘合代码。
    NSOperation是对GCD的封装的面向对象,因此比起GCD的灵活性,封装后使用上可能相对方便,缺也有不少的局限性。
    那么具体有哪些优点呢?

    1、确定执行的顺序

    NSOperation与GCD一样,都可以增加优先级queuePriority,会影响到出列和执行block的顺序,但是影响的只是执行顺序开始而已,并不代表执行顺序结束会按照优先级来。比如,两个人吃苹果,你优先级高, 你先开始吃,我后面吃,但是不代表你比我早吃完,
    这就表示,优先级只能用来对非依赖性的操作进行分类,而不是用来对不同对象之间的依赖关系管理。其优先级如下:默认NSOperationQueuePriorityNormal

    NSOperationQueuePriorityVeryLow
    NSOperationQueuePriorityLow
    NSOperationQueuePriorityNormal
    NSOperationQueuePriorityHigh
    NSOperationQueuePriorityVeryHigh
    

    NSOperation提供给用户管理队列执行依赖顺序的方式addOperation,用此方法添加依赖关系。就可以确保来任务执行顺序。例如我依赖你,你先吃苹果,你吃完我才开始吃苹果。

    注意: addOperation的对象执行的时候,不能再被依赖的对象执行之前。同时也不能添加相互依赖,否则会造成死锁。

    例如下代码,operation1 依赖于operation2 但是operation1 被先执行,直接崩溃

    NSOperationQueue *queue1 = [NSOperationQueue new];
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];
    [operation1 addDependency:operation2];
    [operation1 start];
    [operation2 start];
    

    例如下代码,两个之间添加相互依赖不管谁先执行都会死锁。

    NSOperationQueue *queue1 = [NSOperationQueue new];
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test2) object:nil];
    [operation1 addDependency:operation2];
    [operation2 addDependency:operation1];
    [operation1 start];
    [operation2 start];
    
    2、响应取消命令

    Operation完成任务并不意味着完成了任务,操作也能取消。取消操作对象会将对象留在队列中,但会通知对象它应该尽快停止其任务。对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止它正在执行的操作,并将自己标记为已完成。对于排队但尚未执行的操作,队列仍必须调用操作对象的start方法,以便它可以处理取消事件并将自身标记为已完成。
    取消操作不会立即强制它停止正在进行的操作。开发人员自己去判断isCancelled并根据需要中止。默认实现NSOperation包括检查取消。例如,如果在start调用其方法之前取消操作,则该start方法将退出而不启动任务。
    举几个例子,当还没开始执行时,取消操作会直接不执行:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"开始");
        NSOperationQueue *queue1 = [NSOperationQueue new];
        _operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
        [queue1 addOperation:_operation1];
        [_operation1 cancel];
        NSLog(@"结束");
    }
    
    - (void)test {
        if (_operation1.isCancelled) {
            NSLog(@"取消了");
        } else {
            NSLog(@"1 当前的线程为--%@", [NSThread currentThread]);
        }
    }
    
    2019-04-25 15:54:52.371154+0800 test_1[23375:1924354] 开始
    2019-04-25 15:54:54.372595+0800 test_1[23375:1924354] 结束
    

    当正在进行中的操作时,取消操作需要手动结束

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"开始");
        NSOperationQueue *queue1 = [NSOperationQueue new];
        _operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
        [queue1 addOperation:_operation1];
        sleep(2);
        [_operation1 cancel];
        NSLog(@"结束");
    }
    
    - (void)test {
        sleep(3);
        if (_operation1.isCancelled) {
            NSLog(@"取消了");
        } else {
            NSLog(@"1 当前的线程为--%@", [NSThread currentThread]);
        }
    }
    
    2019-04-25 15:54:52.371154+0800 test_1[23375:1924354] 开始
    2019-04-25 15:54:54.372595+0800 test_1[23375:1924354] 结束
    2019-04-25 15:54:55.376668+0800 test_1[23375:1924399] 取消了
    
    3、符合KVO的属性

    NSOperation的属性与KVO和KVC兼容并有其性质。这就表示,这些属性不管在哪个线程都是实时变化的,又KVO通知其变化并改变状态。根据用户的操作和系统运行。让用户可以对线程的变化做响应处理。

    isCancelled - 只读
    // 让用户知道被请求的操作的取消,需要用户手动处理
    
    isAsynchronous - 只读
    // 是否是异步执行
    
    isExecuting - 只读
    // 让用户了解操作是否正在积极分配给它的任务。
    
    isFinished - 只读
    // 让用户知道操作对象成功完成了它的任务或遭到取消,正在退出。在key更改为之前,操作对象不会清除依赖关系。
    // 因此在该值为true之前对于保持队列不通过正在进行或取消的操作进行备份至关重要。
    
    isReady - 只读
    // 让用户知道,当一个操作已准备好执行。该属性包含操作现在可以立即执行的值,或者是否仍有未完成的操作依赖于该操作。
    
    dependencies - 只读
    // 在当前对象开始执行之前必须完成执行的操作对象数组。
    
    queuePriority - 可读和可写
    // 队列等级
    
    completionBlock - 可读和可写
    // 操作主要任务完成后执行的块。
    

    线程之间的通信和同步

    同步和异步线程

    NSOperation子类NSInvocationOperationNSBlockOperation,本身是不具备开辟线程的能力,并且只执行一次。

    NSLog(@"开始");
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation start];
    NSLog(@"结束");
    
    2019-04-25 16:38:10.577710+0800 test_1[23677:1936783] 开始
    2019-04-25 16:38:10.577894+0800 test_1[23677:1936783] 当前的线程为--<NSThread: 0x280b41a80>{number = 1, name = main}
    2019-04-25 16:38:10.577912+0800 test_1[23677:1936783] 结束
    
    NSLog(@"开始");
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    [operation start];
    NSLog(@"结束");
    
    2019-04-25 16:39:12.363320+0800 test_1[23704:1937254] 开始
    2019-04-25 16:39:12.363537+0800 test_1[23704:1937254] 当前的线程为--<NSThread: 0x280eb9a80>{number = 1, name = main}
    2019-04-25 16:39:12.363556+0800 test_1[23704:1937254] 结束
    

    两种方式结果是一样的,都是在主线程执行,并且是串行的方式。
    想要实现开辟新线程的能力,则需要与队列NSOperationQueue结合使用。

    NSLog(@"开始");
    NSOperationQueue *queue1 = [NSOperationQueue new];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"当前的线程为--%@", [NSThread currentThread]);
    }];
    [queue1 addOperation:operation];
    NSLog(@"结束");
    
    2019-04-25 16:43:22.327504+0800 test_1[23752:1938565] 开始
    2019-04-25 16:43:22.327656+0800 test_1[23752:1938565] 结束
    2019-04-25 16:43:22.327763+0800 test_1[23752:1938650] 当前的线程为--<NSThread: 0x2823ecd00>{number = 3, name = (null)}
    
    使用 addExecutionBlock 和 maxConcurrentOperationCount

    NSBlockOperation提供一个方式addExecutionBlock,可以用这种方式可以实现线程安全,即能让程序在线程中串行执行的同时又有开辟新线程的能力:

    NSLog(@"开始");
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation start];
    NSLog(@"结束");
    
    2019-04-25 16:44:47.825744+0800 test_1[23773:1939191] 开始
    2019-04-25 16:44:47.825935+0800 test_1[23773:1939191]  1 当前的线程为--<NSThread: 0x283bdee80>{number = 1, name = main}
    2019-04-25 16:44:47.825953+0800 test_1[23773:1939235]  2 当前的线程为--<NSThread: 0x283bbc380>{number = 3, name = (null)}
    2019-04-25 16:44:47.825961+0800 test_1[23773:1939191]  3 当前的线程为--<NSThread: 0x283bdee80>{number = 1, name = main}
    2019-04-25 16:44:47.825981+0800 test_1[23773:1939235]  4 当前的线程为--<NSThread: 0x283bbc380>{number = 3, name = (null)}
    2019-04-25 16:44:47.826010+0800 test_1[23773:1939191] 结束
    
    使用 addOperationWithBlock 和 maxConcurrentOperationCount

    NSOperationQueue 提供了方法addOperationWithBlock 可以用来实现异步队列,提供了maxConcurrentOperationCount用来设置最大并发数,通过这些可以实现GCD的并发请求的控制。

    NSLog(@"开始");
    NSOperationQueue *queue = [NSOperationQueue new];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSLog(@"结束");
    
    同步队列

    我们就以NSBlockOperation方式为例来实现,实现串行同步。

    NSLog(@"开始");
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation start];
    [operation2 start];
    [operation3 start];
    [operation4 start];
    NSLog(@"结束");
    
    2019-04-25 17:21:09.496324+0800 test_1[24021:1954586] 开始
    2019-04-25 17:21:09.496545+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
    2019-04-25 17:21:09.496579+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
    2019-04-25 17:21:09.496599+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
    2019-04-25 17:21:09.496615+0800 test_1[24021:1954586]  1 当前的线程为--<NSThread: 0x28194da80>{number = 1, name = main}
    2019-04-25 17:21:09.496625+0800 test_1[24021:1954586] 结束
    
    串行异步

    同样的使用NSOperationQueue 来实现异步方式。
    第一种方式:

    NSLog(@"开始");
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 0 当前的线程为--%@", [NSThread currentThread]);
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
        }];
        NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
        }];
        NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
        }];
        NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
        }];
        [operation start];
        [operation2 start];
        [operation3 start];
        [operation4 start];
    }];
    [queue addOperation:op];
    NSLog(@"结束");
    
    2019-04-25 17:30:43.933648+0800 test_1[24080:1958061] 开始
    2019-04-25 17:34:04.869315+0800 test_1[24095:1959001] 结束
    2019-04-25 17:30:43.933959+0800 test_1[24080:1958076]  0 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
    2019-04-25 17:30:43.934065+0800 test_1[24080:1958076]  1 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
    2019-04-25 17:30:43.934100+0800 test_1[24080:1958076]  2 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
    2019-04-25 17:30:43.934118+0800 test_1[24080:1958076]  3 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
    2019-04-25 17:30:43.934134+0800 test_1[24080:1958076]  4 当前的线程为--<NSThread: 0x281ca4380>{number = 3, name = (null)}
    

    第二种方式通过addOperationWithBlock,并且控制maxConcurrentOperationCount最大并发数为1

     NSLog(@"开始");
    NSOperationQueue *queue = [NSOperationQueue new];
    queue.maxConcurrentOperationCount = 1;
    for (int i=0; I<5; i++) {
        [queue addOperationWithBlock:^{
            NSLog(@" %d 当前的线程为--%@",i , [NSThread currentThread]);
        }];
    }
    NSLog(@"结束");
    
    2019-04-26 11:33:45.983327+0800 test_1[27906:2321076] 开始
    2019-04-26 11:33:45.983518+0800 test_1[27906:2321076] 结束
    2019-04-26 11:33:45.983595+0800 test_1[27906:2321143]  0 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
    2019-04-26 11:33:45.983700+0800 test_1[27906:2321143]  1 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
    2019-04-26 11:33:45.983738+0800 test_1[27906:2321143]  2 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
    2019-04-26 11:33:45.983761+0800 test_1[27906:2321143]  3 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
    2019-04-26 11:33:45.983785+0800 test_1[27906:2321143]  4 当前的线程为--<NSThread: 0x282b9a140>{number = 3, name = (null)}
    

    第三种方式,利用NSOperationQueueaddDependency相互依赖的方式,但是这种方式虽然是异步串行缺不一定在同一个异步线程中:

    NSLog(@"开始");
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
    }];
    [operation4 addDependency:operation3];
    [operation3 addDependency:operation2];
    [operation2 addDependency:operation];
    [queue addOperation:operation];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
    NSLog(@"结束");
    
    2019-04-26 11:40:32.095244+0800 test_1[27947:2323900] 开始
    2019-04-26 11:40:32.095411+0800 test_1[27947:2323900] 结束
    2019-04-26 11:40:32.095888+0800 test_1[27947:2323953]  1 当前的线程为--<NSThread: 0x2838f7300>{number = 3, name = (null)}
    2019-04-26 11:40:32.096086+0800 test_1[27947:2323955]  2 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
    2019-04-26 11:40:32.096119+0800 test_1[27947:2323955]  3 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
    2019-04-26 11:40:32.096144+0800 test_1[27947:2323955]  4 当前的线程为--<NSThread: 0x283893740>{number = 4, name = (null)}
    
    并行异步

    通过队列NSOperationQueue 将多个Operation实现并行:

    NSLog(@"开始");
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 1 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 2 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 3 当前的线程为--%@", [NSThread currentThread]);
    }];
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@" 4 当前的线程为--%@", [NSThread currentThread]);
    }];
    [queue addOperation:operation];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
    NSLog(@"结束");
    
    2019-04-25 17:37:21.282807+0800 test_1[24110:1959919] 开始
    2019-04-25 17:37:21.282996+0800 test_1[24110:1959919] 结束
    2019-04-25 17:37:21.283182+0800 test_1[24110:1959950]  1 当前的线程为--<NSThread: 0x28133a940>{number = 3, name = (null)}
    2019-04-25 17:37:21.283216+0800 test_1[24110:1959949]  4 当前的线程为--<NSThread: 0x28131c6c0>{number = 5, name = (null)}
    2019-04-25 17:37:21.283221+0800 test_1[24110:1959948]  2 当前的线程为--<NSThread: 0x281338800>{number = 6, name = (null)}
    2019-04-25 17:37:21.283243+0800 test_1[24110:1959951]  3 当前的线程为--<NSThread: 0x28133a900>{number = 4, name = (null)}
    

    如果使用addOperationWithBlock,并且取消掉最大并发数maxConcurrentOperationCount,或者设置最大并发数大于1,则就是并行队列了。

    NSLog(@"开始");
    NSOperationQueue *queue = [NSOperationQueue new];
    for (int i=0; i<20; i++) {
        [queue addOperationWithBlock:^{
            NSLog(@" %d 当前的线程为--%@",i , [NSThread currentThread]);
        }];
    }
    NSLog(@"结束");
    
    2019-04-25 17:37:21.282807+0800 test_1[24110:1959919] 开始
    2019-04-25 17:37:21.282996+0800 test_1[24110:1959919] 结束
    2019-04-25 17:37:21.283182+0800 test_1[24110:1959950]  1 当前的线程为--<NSThread: 0x28133a940>{number = 3, name = (null)}
    2019-04-25 17:37:21.283216+0800 test_1[24110:1959949]  4 当前的线程为--<NSThread: 0x28131c6c0>{number = 5, name = (null)}
    2019-04-25 17:37:21.283221+0800 test_1[24110:1959948]  2 当前的线程为--<NSThread: 0x281338800>{number = 6, name = (null)}
    2019-04-25 17:37:21.283243+0800 test_1[24110:1959951]  3 当前的线程为--<NSThread: 0x28133a900>{number = 4, name = (null)}
    
    异步执行回到主线程更新UI
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [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]); // 打印当前线程
            }
        }];
    }];
    
    2019-04-26 17:53:20.178591+0800 test_1[819:39160] 1---<NSThread: 0x282ebc6c0>{number = 3, name = (null)}
    2019-04-26 17:53:22.184004+0800 test_1[819:39160] 1---<NSThread: 0x282ebc6c0>{number = 3, name = (null)}
    2019-04-26 17:53:24.185889+0800 test_1[819:39134] 2---<NSThread: 0x282ef2e80>{number = 1, name = main}
    2019-04-26 17:53:26.187373+0800 test_1[819:39134] 2---<NSThread: 0x282ef2e80>{number = 1, name = main}
    

    相关文章

      网友评论

          本文标题:iOS多线程 - NSOperation详解

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