美文网首页
多线程技术:NSOperation 与 NSOperationQ

多线程技术:NSOperation 与 NSOperationQ

作者: dc630f46ee2d | 来源:发表于2017-10-29 14:25 被阅读0次

为什么要学习NSOperation和NSOperationQueue来实现多线程

  1. GCD功能更强大,可以cancel一个任务,可以在任务之间添加依赖。
  2. 主流的库常常使用这种技术完成复杂的设计,从侧面证明这种技术很有用,要看懂其他牛逼的库,也需要这个技术的支撑。

NSOperation的种类

NSOperation是一个抽象类,系统预设了两种子类NSInvocationOperationNSBlockOperation,同时我们也可以继承NSOperation写自己的Operation

  1. NSInvocationOperation

  2. NSBlockOperation

  3. 自定义NSOperation的子类

不使用NSOperationQueue的NSOperation(使用场景非常少,苹果也不推荐)

单独使用NSOperation而不配合NSOperationQueue基本不能实现多线程,说基本是因为一种特殊情况下依然是可以实现多线程,但是即便如此,也使用较小,所以这个不是我们学习的重点,简单的总结一下

启动 ,不使用NSOperationQueue,启动就是调用NSOperation自己的方法 start

  1. 使用NSInvocationOperation, 不开新线程,顺序执行
- (void)viewDidLoad {
    [super viewDidLoad];


    NSLog(@"start");
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    [invocationOperation start];
    NSLog(@"end");
}
- (void)invocationOperation {
    NSLog(@"do invocationOperation %@", [NSThread currentThread]);
}

输出结果

2017-10-29 09:01:58.902 TESTOption[15342:4271780] start
2017-10-29 09:01:58.902 TESTOption[15342:4271780] do invocationOperation <NSThread: 0x60800006c9c0>{number = 1, name = main}
2017-10-29 09:01:58.903 TESTOption[15342:4271780] end
  1. 使用NSBlockOperationblockOperationWithBlock,不开新线程,顺序执行
    NSLog(@"start %@", [NSThread currentThread]);
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"do something 1 %@", [NSThread currentThread]);
    }];
    [blockOperation start];

    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"do something 2 %@", [NSThread currentThread]);
    }];
    [blockOperation2 start];
    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"do something 2 %@", [NSThread currentThread]);
    }];
    [blockOperation3 start];    
    NSLog(@"end %@", [NSThread currentThread]);

输出结果

2017-10-27 23:12:33.049 MLeaksFinderDemo[13830:3493348] start <NSThread: 0x6080000734c0>{number = 1, name = main}
2017-10-27 23:12:33.050 MLeaksFinderDemo[13830:3493348] do something 1 <NSThread: 0x6080000734c0>{number = 1, name = main}
2017-10-27 23:12:33.051 MLeaksFinderDemo[13830:3493348] do something 2 <NSThread: 0x6080000734c0>{number = 1, name = main}
2017-10-27 23:12:33.051 MLeaksFinderDemo[13830:3493348] do something 2 <NSThread: 0x6080000734c0>{number = 1, name = main}
2017-10-27 23:12:33.052 MLeaksFinderDemo[13830:3493348] end <NSThread: 0x6080000734c0>{number = 1, name = main}
  1. 使用NSBlockOperationaddExecutionBlock,开新线程,并发,唯一一个
    NSLog(@"start %@", [NSThread currentThread]);
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"do something 1 %@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"do something 1 part2 %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"do something 1 part3 %@", [NSThread currentThread]);
        
    }];
    [blockOperation start];

输出结果

2017-10-28 04:31:42.580 MLeaksFinderDemo[14178:3678696] do something 1 part1 <NSThread: 0x60800007e580>{number = 1, name = main}
2017-10-28 04:31:42.580 MLeaksFinderDemo[14178:3678696] do something 1 part3 <NSThread: 0x60800007e580>{number = 1, name = main}
2017-10-28 04:31:42.581 MLeaksFinderDemo[14178:3678734] do something 1 part2 <NSThread: 0x60000026c540>{number = 3, name = (null)}
  1. 使用自定义Operation ,顺序执行

重写main方法 实现基本操作

@implementation MyOperation
- (void)main {
    NSLog(@"do something %@", [NSThread currentThread]);
}
@end

调用MyOperation

    NSLog(@"start %@", [NSThread currentThread]);
    MyOperation *operation1 = [[MyOperation alloc] init];
    [operation1 start];
    MyOperation *operation2 = [[MyOperation alloc] init];
    [operation2 start];
    NSLog(@"end %@", [NSThread currentThread]);

使用NSOperationQueue的NSOperation(使用场景多)

NSOperationQueue的种类

NOperationQueue分为两种,系统预留的主队列和自定义队列,自定义队列不需要继承直接使用[[NOperationQueue alloc] init]创建,主队列直接使用[NSOperationQueue mainQueue]访问即可,放在[NSOperationQueue mainQueue]的任务都会在主线程上执行。

串行和并行

[NSOperationQueue mainQueue]是串行队列,放在上面的任务都会在主线程上执行
[[NOperationQueue alloc] init]创建的队列默认是并行的,它有一个属性是

启动任务,使用addOpearation:

当一个nsoperation对象加入到operationQueue数组时,operation的启动不再用 operation自己决定。而是由operationQueue决定,一旦一个operation被加入operationQueue时,operation将会在很短的时候内执行。
使用addOperation:方法添加然后自动会启动任务。

MyOperation *operation = [[MyOperation alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

NSBlockOperation和NSInvocationOperation使用实际例子

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImageView *imageView = [[UIImageView alloc] init];
    imageView.frame = CGRectMake(200, 200, 100, 100);

    [self.view addSubview:imageView];
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue = operationQueue;
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"use blockOperation %@", [NSThread currentThread]);
    }];
    NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    
    NSLog(@"start %@", [NSThread currentThread]);
    [operationQueue addOperation:operation];
    [operationQueue addOperation:invocation];

}

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

输出结果

2017-10-29 11:58:44.278 MLeaksFinderDemo[16434:4589155] start <NSThread: 0x608000062a00>{number = 1, name = main}
2017-10-29 11:58:44.279 MLeaksFinderDemo[16434:4589195] use invocationOperation <NSThread: 0x6080000711c0>{number = 3, name = (null)}
2017-10-29 11:58:44.279 MLeaksFinderDemo[16434:4589198] use blockOperation <NSThread: 0x6080000710c0>{number = 4, name = (null)}
###自定义Operation使用main 方法

3个log信息在不同线程,证明现在确实实现了多线程。

自定义Operation

NSOperation有两个方法startmain方法。它们在启动后会顺序执行。
我们可以写一个自定义的Operation的子类来验证一下。

@implementation MyOperation
- (void)start {
    NSLog(@"start");

    [super start];
}

- (void)main {
     NSLog(@"main");
    [super main];

}
@end
@implementation DetailViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.operationQueue = [[NSOperationQueue alloc] init];
    MyOperation *operation = [[MyOperation alloc] init];
    [self.operationQueue addOperation:operation];
    
}
@end

输出

2017-10-29 12:09:33.250 MLeaksFinderDemo[16565:4629369] start
2017-10-29 12:09:35.194 MLeaksFinderDemo[16565:4629369] main

使用自定义Operation来实现多线程,按照官网的教材,约定有两种方法。
重写main或者start方法中的其中一个。main比较简单

main方法实现定制

  • 继承NSOperation类
  • 重写“main”方法
  • 在“main”方法中创建一个“autoreleasepool”
  • 将你的代码放在“autoreleasepool”中

正确响应取消事件

operation开始执行之后,会一直执行任务直到完成,或者显式地取消操作。取消可能发生在任何时候,甚至在operation执行之前。尽管NSOperation提供了一个方法,让应用取消一个操作,但是识别出取消事件则是我们自己的事情。如果operation直接终止, 可能无法回收所有已分配的内存或资源。因此operation对象需要检测取消事件,并优雅地退出执行
NSOperation对象需要定期地调用isCancelled方法检测操作是否已经被取消,如果返回YES(表示已取消),则立即退出执行。不管是自定义NSOperation子类,还是使用系统提供的两个具体子类,都需要支持取消。isCancelled方法本身非常轻量,可以频繁地调用而不产生大的性能损失
以下地方可能需要调用isCancelled:

  • 在执行任何实际的工作之前
  • 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
  • 代码中相对比较容易中止操作的任何地方
- (void)main {
    @autoreleasepool {
        if (self.isCancelled) {
            return;
        }
        for (NSInteger i = 0; i < 10000; i++) {
            if (self.isCancelled) {
                return;
            }
            NSLog(@"do something %ld", i / 100);
        }
    }

}
@end

总结

NSOperation本身内容比较繁杂,另外开一个文章介绍更多复杂和高级功能

相关文章

网友评论

      本文标题:多线程技术:NSOperation 与 NSOperationQ

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