NSOperation的多线程使用以及和GCD的对比

NSOperation介绍

基本使用介绍
NSOperation类是一个抽象类,用于封装与单个任务相关联的代码和数据。 因为它是抽象的,你不直接使用这个类,而是子类或使用系统定义的子类之一(NSInvocationOperation或NSBlockOperation)来执行实际任务。 尽管是抽象的,NSOperation的基本实现确实包括重要的逻辑来协调您的任务的安全执行。 这种内置逻辑的存在允许您专注于任务的实际实现,而不是在确保其与其他系统对象正确工作所需的粘合代码上。

GCD队列类型

  • 并发队列
    • 自己创建的
    • 全局
  • 串行队列
    • 主队列
    • 自己创建

NSOperationQueue的队列类型

  • 主队列
    • [NSOperationQueue mainQueue]
    • 凡是添加到主队列的任务(NSOperation),都会放到主线程中执行
  • 非主队列(其他队列)
    • [[NSOperationQueue alloc] init]
    • 同时包含了串行并发功能
    • 添加到这个队列的任务(NSOperation)自定会放到子线程执行

基本用法用法介绍

// 通过队列的方式创建
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

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

    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test1) object:nil];

    // 自定义
    MKJOperation *op3 = [[MKJOperation alloc] init];
    // 重写main函数即可
//    - (void)main
//    {
//        NSLog(@"我是自定义的任务%@,%d",[NSThread currentThread],[self isConcurrent]);
//    }

    // 默认实在子线程  而且不需要调用start方法
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
//    2016-12-23 08:51:06.400 NSOperation[1439:67271] 任务1<NSThread: 0x608000265ec0>{number = 3, name = (null)}
//    2016-12-23 08:51:06.400 NSOperation[1439:67274] 任务5,<NSThread: 0x600000263d40>{number = 5, name = (null)}
//    2016-12-23 08:51:06.400 NSOperation[1439:67272] 我是自定义的任务<NSThread: 0x600000262ec0>{number = 4, name = (null)}



需要注意的是对于自定义Operation的实现部分,首先我们已经知道GCD里面有异步串行和异步并发,那么NSOperation是GCD的封装,自然也可以,通过暴露给外部的 isConcurrent来检测是哪一个。

文档这么说:
The value of this property is YES for operations that run asynchronously with respect to the current thread or NO for operations that run synchronously on the current thread. The default value of this property is NO.

也就是默认是NO,异步串行的,文档对自定义NSOperation重写有两种,就是根据这个类型来的,默认的时候是NO,异步串行,那么只要实现main一个函数即可
在这个方法中,你放置执行给定任务所需的代码。 当然,您还应该定义一个自定义初始化方法,以便更容易创建自定义类的实例。 您可能还需要定义getter和setter方法以从操作访问数据。 但是,如果您定义了自定义的getter和setter方法,您必须确保这些方法可以从多个线程安全地调用。

属性之maxConcurrentOperationCount

默认是-1 由系统控制
NSOperationQueueDefaultMaxConcurrentOperationCount = -1;

加到NSOperationQueue的任务默认都是异步并发执行的,当设置该属性大于1的时候就是,如果你要实现串行任务执行,queue.maxConcurrentOperationCount = 1; 一句话搞定

属性之suspended

// 通过队列的方式创建
    self.queue = [[NSOperationQueue alloc] init];
    self.queue.maxConcurrentOperationCount = 1;
    [self.queue addOperationWithBlock:^{
        for (NSInteger i =0; i < 10000; i ++) {
            NSLog(@"任务1%@,%ld",[NSThread currentThread],i);
        }
    }];

    [self.queue addOperationWithBlock:^{
        for (NSInteger i =0; i < 1000; i ++) {
            NSLog(@"任务2%@",[NSThread currentThread]);
        }
    }];

    [self.queue addOperationWithBlock:^{
        for (NSInteger i =0; i < 1000; i ++) {
            NSLog(@"任务3%@",[NSThread currentThread]);
        }
    }];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.queue.suspended = !self.queue.suspended;
}



这里需要注意的是,调用suspended属性的时候为YES的时候,前面的执行10000次打印不是中途停止(例如打印到5550次停止)该任务还是会继续打印完10000次,而是任务2和任务3被挂起,不在执行,当再一次被点击的时候,会继续打印之后的操作,根据这个特性可以优化tableview,当滑动的时候把所有任务都挂起,优化用户体验,滑动结束的时候继续执行suspended = NO即可。

方法之cancelAllOperations

和上面的suspended一样,调用这个方法的时候,还是会继续执行完当前任务,取消掉后面的所有任务。如果显示地调用类似各种系统自带的Operation加入到队列里面去,调用cancel方法是没有问题,但是当你自定义的时候,例如你有好几个耗时操作,你都重写了main 方法,在里面进行coding,这个时候你在外面调用cancel的时候是不会取消里面的任务的,你可以自己试试,他会把被这个Operation包裹起来的所有任务执行完,取消的是之后的Operation,如果你要取消自定义任务里面的单个耗时操作,你只需要这么做就好了

- (void)main
{
    // 耗时操作1
    for (NSInteger i =0; i < 5000; i ++) {
        NSLog(@"任务1%@,%ld",[NSThread currentThread],i);
    }
    // 如果不进行判断是无法进行取消的,还是会全部执行
    if (self.isCancelled) {
        return;
    }
    // 耗时任务2
    for (NSInteger i =0; i < 1000; i ++) {
        NSLog(@"任务2%@,%ld",[NSThread currentThread],i);
    }
    if (self.isCancelled) {
        return;
    }
    // 耗时任务3
    for (NSInteger i =0; i < 1000; i ++) {
        NSLog(@"任务3%@,%ld",[NSThread currentThread],i);
    }
}

为什么这么做,官方介绍如下
特别是,你的主要任务代码应该定期检查被取消的属性的值。 如果属性报告值YES,您的操作对象应尽快清除并退出。
You should always support cancellation semantics in any custom code you write. In particular, your main task code should periodically check the value of the cancelled property. If the property reports the value YES, your operation object should clean up and exit as quickly as possible. If you implement a custom start method, that method should include early checks for cancellation and behave appropriately. Your custom start method must be prepared to handle this type of early cancellation.

方法之addDependency任务依赖

    // 通过队列的方式创建
    self.queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务1,%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务2,%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(0.02);
        NSLog(@"任务3,%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务4,%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务5,%@",[NSThread currentThread]);
    }];
    // 4必定在1 2 3都执行完之后才能执行,而1,2,3,5还是无序的
    [op4 addDependency:op1];
    [op4 addDependency:op2];
    [op4 addDependency:op3];

    [self.queue addOperation:op1];
    [self.queue addOperation:op2];
    [self.queue addOperation:op3];
    [self.queue addOperation:op4];
    [self.queue addOperation:op5];

    // 未设置依赖
//    2016-12-23 10:37:00.346 NSOperation[3184:204706] 任务4,<NSThread: 0x600000071480>{number = 6, name = (null)}
//    2016-12-23 10:37:00.346 NSOperation[3184:204704] 任务2,<NSThread: 0x608000066c40>{number = 4, name = (null)}
//    2016-12-23 10:37:00.346 NSOperation[3184:204703] 任务3,<NSThread: 0x600000071440>{number = 3, name = (null)}
//    2016-12-23 10:37:00.346 NSOperation[3184:204717] 任务1,<NSThread: 0x608000073d40>{number = 5, name = (null)}
//    2016-12-23 10:37:00.347 NSOperation[3184:204704] 任务5,<NSThread: 0x608000066c40>{number = 4, name = (null)}

    // 设置依赖

//    2016-12-23 10:39:38.817 NSOperation[3266:209106] 任务2,<NSThread: 0x6000002690c0>{number = 4, name = (null)}
//    2016-12-23 10:39:38.817 NSOperation[3266:209105] 任务1,<NSThread: 0x60800007fb00>{number = 3, name = (null)}
//    2016-12-23 10:39:38.817 NSOperation[3266:209108] 任务3,<NSThread: 0x60800026a240>{number = 5, name = (null)}
//    2016-12-23 10:39:38.817 NSOperation[3266:209124] 任务5,<NSThread: 0x60800026af80>{number = 6, name = (null)}
//    2016-12-23 10:39:38.819 NSOperation[3266:209108] 任务4,<NSThread: 0x60800026a240>{number = 5, name = (null)}

牛逼之处在于可以不同队列进行任务相互依赖
多队列任务贯穿依赖

属性之comoletionBlock

@property (nullable, copy) void (^completionBlock)(void)

op4.completionBlock = ^{
        NSLog(@"任务4完成了");
    };

使用场景:多图片下载合成

self.queue = [[NSOperationQueue alloc] init];
    // 任务1
    NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:@"http://sc4.hao123img.com/data/2016-09-07/1_e5b664a087a52159bf21afa364f5e285_0"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        weakSelf.image1 = [UIImage imageWithData:data];
    }];

    // 任务2
    NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
        // 这里可以一样是耗时的网络请求,暂时处理成本地的
        weakSelf.image2 = [UIImage imageNamed:@"Play"];
    }];

    // 合成任务
    NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
        UIGraphicsBeginImageContext(CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width));
        [weakSelf.image1 drawInRect:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width)];
        [weakSelf.image2 drawInRect:CGRectMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.width/2, 30, 40)];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            weakSelf.imageView.image = image;
        }];

    }];
    // 设置依赖
    [combine addDependency:download1];
    [combine addDependency:download2];
    // 加入队列
    [self.queue addOperation:download1];
    [self.queue addOperation:download2];
    [self.queue addOperation:combine];

这里写图片描述


GCD实现该方法需要用到Group,但是NSOperation十分简单就能实现依赖,操作起来更加面向对象

GCD和NSOperation的区别

  • GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。
    在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;
    而Operation作为一个对象,为我们提供了更多的选择;
    GCD面向C,NSOperation是GCD的封装,面向对象

  • 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,
    已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,
    但需要许多复杂的代码);
    NSOperation可以取消任务,GCD不行

  • NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,
    这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
    超级强大的依赖关系,而且能设置不同队列之之间的任务依赖

  • 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,
    这样子能比GCD更加有效地掌控我们执行的后台任务;
    NSOperation用KVO监听完成,取消,开始,挂起等状态,比GCD更能掌控后台操作

  • 在NSOperation中,我们能够设置NSOperation的priority优先级,
    能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,
    如果要区分block任务的优先级,也需要大量的复杂代码;
    NSOperation可以设置任务之间的优先级,GCD只能设置不同队列之间的优先级,如果要做,需要大量代码

  • 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,
    提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,
    能够在其之上添加更多自定制的功能。
    NSOperation抽象类,我们能自定义子类进行重写,更具自由度,而且有可复用性

    Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;
    GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手

GCD用法和介绍传送门

runtime面试问题以及常用用法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值