iOS 多线程系列 -- NSOperation

iOS 多线程系列 -- 基础概述
iOS 多线程系列 -- pthread
iOS 多线程系列 -- NSThread
iOS 多线程系列 -- GCD全解一(基础)
iOS 多线程系列 -- GCD全解二(常用方法)
iOS 多线程系列 -- GCD全解三(进阶)
iOS 多线程系列 -- NSOperation
测试Demo的GitHub地址

上一篇我们介绍过GCD,这一篇我们学习一下iOS另一种多线程技术--NSOperation技术.

NSOperation也是苹果提供给我们的一套多线程解决方案。实际上它也是基于GCD的一层OC封装,在拥有GCD很多优点的同时,比GCD拥有更强的可控性和代码可读性.和GCD
一样,NSOperation一样不需要我们管理线程的创建销毁等事情,系统会自动根据CPU负载情况,创建合适的线程去执行任务;但同时, NSOperation提供了更多GCD不提供的控制线程属性,例如我们可以控制最大并发数量、取消队列中还没有执行的任务(注意是还没有执行的任务)等

NSOperation

1.基础知识

1.1 认识NSOperation类

  • NSOperation除了代表iOS多线程的一种技术,还是这个技术中的一个重要类。
  • NSOperation本身是抽象基类,不能直接使用,但是他封装了需要执行的操作和执行操作所需的数据方法等。在NSOperation基础上,系统提供了两个子类NSBlockOperation和NSInvocationOperation供我们具体使用,当然,我们也可以自己封装自定义的NSOperation。

1.2 NSOperation的常用方法、属性介绍

  • setCompletionBlock 监听NSOperation的完成,调用NSOperation的setCompletionBlock方法来设置操作完成后想做的事情
    • completionBlock 会在后台线程执行,不会再主线程执行,所以如果需要更新UI,需要注意线程
    • completionBlock 不能接受参数,没有返回值,因此在使用的时候,会有很多限制
  • cancel NSOperation实例调用cancel方法,取消operation执行,但并不是绝对取消任务,如果operation加入到Queue中或者operation已经start了,则无法取消成功。同时还会设置isCancelled属性为YES,这个属性在自定义NSOperation的时候很重要
1)- (id)init;//NSOperation实例初始化方法,用于创建一个NSOperation实例。
2)- (void)start;//该方法是operation的起点,如果需要创建并发operation必须,覆盖start方法。同时调用start方法,会默认做各种验证。
3)- (BOOL)isCancelled;//该property,是用于标示某个operation是否cancel。对于多线程来说需要不断检测这个值。
4)- (void)cancel;//调用cancel方法会取消一个operation,但是如果operation加入到Queue中或者operation已经start了,则无法取消成功,调用cancel也不一定立即执行cancel操作,需要等待时间周期。
5)- (BOOL)isExecuting;//判定operation是否正在执行。
6)- (BOOL)isFinished;//判定operation是否完成,cancel掉某个operation,也会将该operation的该字段设置成为YES。
7)- (BOOL)isConcurrent;//判定该线程是否是并发线程,即调用该operation的start方法的线程是否与operation所在线程相同。
8)- (BOOL)isReady;//在start方法开始之前,需要确定operation是否ready,默认为YES,如果该operation没有ready,则不会start。
9)- (void)addDependency:(NSOperation *)op;//该方法用于配置operation之间的依赖关系,涉及执行顺序稍后会介绍。如果不是手动调用start去执行operation,一定要在将其加入到Queue之前做好依赖,因为一旦加入到Queue中,其也许很快会执行,依赖关系将不会起作用。
10)- (void)removeDependency:(NSOperation *)op;//相对应add,其为移除两个operation之间的依赖关系。
11)- (NSArray *)dependencies;//获取operation的依赖关系的数组。
12)- (NSOperationQueuePriority)queuePriority;//如果将operation加入到Queue中,设定其在Queue中的优先级,优先级高的先执行的概率大,但并不代表一定会先执行,执行顺序稍后介绍。
13)- (void)setQueuePriority:(NSOperationQueuePriority)p;//setter方法。
14)- (void (^)(void))completionBlock NS_AVAILABLE(10_6, 4_0);//在operation完成之后会调用completionBlock,你可以自定义执行行为。
15)- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);//设定是否等待operation执行结束,如果为YES,该线程会一直等待operation执行结束,才会执行接下来的代码。
16)- (double)threadPriority NS_AVAILABLE(10_6, 4_0);//设定operation的线程优先级,涉及执行顺序稍后介绍。线程优先级默认为0.5,最低为0,最大为1.即使设定了线程优先级,也只能保证其在main方法范围内有效,operation的其他代码仍然执行在默认线程。

2.两个系统提供的NSOperation子类

2.1 NSBlockOperation

  • 能够并发地执行一个或多个block对象,所有相关的block都执行完之后,操作才算完成

  • 创建NSBlockOperation对象

    + (id)blockOperationWithBlock:(void (^)(void))block;
    
  • 通过addExecutionBlock:方法添加更多的操作

    - (void)addExecutionBlock:(void (^)(void))block;
    
  • 注意:

    • 调用start方法,默认会在调用start的线程中执行任务,但是只要NSBlockOperation封装的操作数大于1,调用start方法执行任务,就会异步执行操作;可以在下面示例代码中验证这一点
  • 示例代码:

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ // 在主线程
        NSLog(@"下载1------%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{ // 添加额外的任务(在子线程执行)
        NSLog(@"下载2------%@", [NSThread currentThread]);
    }];
    [op start];//开始执行任务
  • 示例代码打印结果:

2017-07-04 20:01:09.586 Test - 多线程[20794:4540810] 下载1------{number = 1, name = main}
2017-07-04 20:01:09.586 Test - 多线程[20794:4540931] 下载2------{number = 3, name = (null)}

2.2 NSInvocationOperation

  • 基于一个对象和selector来创建操作。如果你已经有现有的方法来执行需要的任务,就可以使用这个类
  • 创建NSInvocationOperation对象

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

  • 线程启动的两种方法(start和NSOperationQueue):
    • 调用start方法开始执行操作

      - (void)start;
      一旦执行操作,就会调用target的sel方法
      
      //可以调用这个方法启动执行任务,但是这样的方法不会启动新的线程,只会在当前线程执行
      [invo start];
      
  • 注意
    • 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作

3.进阶 -- 自定义operation

如果NSInvocationOperation和NSBlockOperation对象不能满足需求, 你可以直接继承NSOperation, 并添加任何你想要的行为。

NOTE 需要自己创建自动释放池(因为如果是异步操作,子线程运行循环默认不工作,不会主动创建自动释放池),但是在查看最新的苹果官方文档时发现,里面指出 “This method will automatically execute within an autorelease pool provided by NSOperation, so you do not need to create your own autorelease pool block in your implementation” 也就是说不需要我们创建autorelease pool。

  • 优点:提供了更加灵活可控的处理方式,如:
    • 我们在自定义任务的时候,可以非常方便的在需要的地方监听NSOperation的isCanceled属性并作出我们需要的处理。

3.1 创建非并发的NSOperation

  • 只需要重载-(void)main这个方法,在这个方法里面执行主任务
  • 在重载main方法里面,添加自动释放池,因为子线程运行循环默认不工作,不会主动创建自动释放池
  • 定期监听operation的isCanceled属性并正确的响应cancel操作即可.下面列举一般在什么地方添加监听:
    • 在开始执行前判断isCancelled,如果为YES直接return中断任务
    • 执行任何实际的工作之前
    • 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
    • 代码中相对比较容易中止操作的任何地方
  • 示例代码如下:
//捕获异常
@try {
    //在这里我们要创建自己的释放池,因为子线程运行循环默认不工作,不会主动创建自动释放池
    @autoreleasepool {
        if (self.isCancelled)
        {
            NSLog(@"0000self.isCancelled = yes , return");
            return;
        }
        for (NSInteger i = 0; i<100; i++) {
            [NSThread sleepForTimeInterval:0.1];
            NSString *string = [NSString stringWithFormat:@"XLOperation1 %zd",i];
            NSLog(@"XLOperation1 -%zd -- isCanceled = %zd-- executing=%zd -- isFinished = %zd -- %@", i, self.isCancelled,self.executing,self.isFinished,[NSThread currentThread]);
        }
    }
}
@catch (NSException *exception) {
    
}

3.2 并发的NSOperation

  • 如果需要创建一个并发的Operation,并手动执行,则需要重写main,start,isCancelled,isReady,isConcurrent,isFinished,isExcluting等方法,并维护每个property的KVO,同时确保每个property的原子性,详细使用示例可以去看SDWebImage中的SDWebImageDownloaderOperation使用
  • 使用步骤:
    • start: 所有并行的 Operations 都必须重写这个方法,然后在你想要执行的线程中手动调用这个方法。注意:任何时候都不能调用父类的start方法。
    • main: 在start方法中调用,但是注意要定义独立的自动释放池与别的线程区分开。
    • isExecuting: 是否执行中,需要实现KVO通知机制。
    • isFinished: 是否已完成,需要实现KVO通知机制。
    • isConcurrent: 该方法现在已经由isAsynchronous方法代替,并且 NSOperationQueue 也已经忽略这个方法的值。
    • isAsynchronous: 该方法默认返回 NO ,表示非并发执行。并发执行需要自定义并且返回 YES。后面会根据这个返回值来决定是否并发。

参考资料:

NSOperation那些事

你可能感兴趣的:(iOS 多线程系列 -- NSOperation)