iOS多线程:NSOperation/NSOperationQueue

NSOperation

NSOperation是基于GCD实现的。相对GCD来说,可控性更强,并且可以添加操作依赖。

NSOperation是一个抽象基类,可以为子类提供线程安全的建立状态、优先级、依赖和取消等操作。

默认情况下,NSOperation单独使用时系统在当前线程同步执行操作

使用NSOperation的方式

  1. 使用系统子类:NSBlockOperationNSInvocationOperation(因为ARC下不是类型安全的,swift中废弃该类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // NSInvocationOperation
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"zh"];
    /* 调用start会马上执行封装好的操作 */
    [operation1 start];

    // NSBlockOperation
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
    NSLog(@"执行了一个操作");
    }];
    /*开始执行任务*/
    [operation2 start];
  2. 自定义NSOperation

    一般通过重写main或者start方法来定义自己的NSOperation对象。

    重写main方法比较简单,我们不需要管理操作的状态属性isExecutingisFinished。当main() 执行完返回的时候,这个操作就结束了。

    1
    2
    3
    4
    5
    6
    7
    class ImageFilterOperation: Operation {
    var inputImage:UIImage?
    var outputImage:UIImage?
    override func main(){
    outputImage = filter(image:inputImage)
    }
    }

NSOperationQueue

  1. 特点

    NSOperationQueue是可以运行多个Operation的队列。

    • 它并不局限于先进先出的队列操作。
    • 提供了多个接口可以实现暂停、继续、中止、优先顺序、依赖等复杂操作。
    • 可以通过设置maxConcurrentOperationCount来区分是串行还是并行。
    • 任务一旦加入队列就开始执行
  2. NSOperationQueue的应用

    由于默认情况下,NSOperation单独使用时系统在当前线程同步执行操作,所以配合NSOperationQueue才能实现异步执行。

    将创建的NSOperation添加到NSOperationQueue,系统会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。

    **在NSOperationQueue中使用NSBlockOperation**:

    • 一个 NSBlockOperation 对象可以封装多个操作
    • NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程,开启的线程数是由系统来决定的。
    • 凡是添加到主队列的操作,都会放到主线程执行。但是使用addExecutionBlock: 添加的额外操作可能会在其他线程执行:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let op1 = BlockOperation.init {
    print("Block:\\(Thread.current)")
    }
    op1.addExecutionBlock {
    print("executionBlock:\\(Thread.current)")
    }

    OperationQueue.main.addOperation {
    op1.start()
    }
    //打印结果:
    Block:<NSThread: 0x600001ede140>{number = 1, name = main}//主线程执行init里的操作
    executionBlock:<NSThread: 0x600001edfec0>{number = 4, name = (null)}//子线程执行executionBlock操作
  3. NSOperationQueue的**cancel()**方法

    NSOperationQueue中有个cancel()方法。它做的唯一工作就是将Operation的isCancelled属性从false改为true

    由于它并不会真正深入代码将某个具体执行的工作暂停,所以必须要利用isCancelled属性的变化来暂停main()方法中的工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class ArraySumOperation:Operation {
    let nums:[Int]
    var sum: Int
    init(nums:[Int]){
    self.nums = nums
    self.sum = 0
    super.init()
    }
    override func main(){
    for num in nums {
    // 根据isCancelled属性来判断是否要暂停操作
    if isCancelled {
    return
    }
    sum += num
    }
    }
    }

    let queue = OperationQueue()
    let someOperation = ArraySumOperation(nums:Array(1...1000))
    // 一旦加入OperationQueue中,Operation就开始执行
    queue.addOperation(someOperation)
    someOperation.cancel()
    // someOperation在彻底完成前已经暂停,sum值小于500500
    someOperation.completionBlock = { print(someOperation.sum)}

自定义队列

  • **添加到自定义队列的操作会自动放到子线程中执行**,同时包含了串行、并发功能。

  • 添加操作到队列有两种方法:addOperation:addOperationWithBlock:

    1
    2
    3
    4
    5
    6
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //添加操作到队列中
    [queue addOperation:someOperation];
    [queue addOperationWithBlock:^{
    //doSomething;
    }];
  1. 最大并发操作数——maxConcurrentOperationCount

    • 用来控制一个队列中同时并发执行的操作数,而不是并发线程的数量。一个操作也并非只能在一个线程中执行。
    • 默认值为-1:表示不限制,可以进行并发执行
    • 等于1:表示队列为串行队列,只能串行执行
    • 大于1:表示队列为并发队列,操作并发执行。但是这个值不应该超过系统限制。
  2. NSOperation的操作依赖

    通过操作依赖可以控制操作之间的执行先后顺序。

    1
    2
    3
    4
    5
    6
    7
    8
    //添加依赖
    -(void)addDependency:(NSOperation *)op;

    //移除依赖,取消当前操作对操作op的依赖
    -(void)removeDependency:(NSOperation *)op;

    //当前操作开始执行之前完成执行的所有操作对象
    @property(readonly, copy) NSArray<NSOperation *> *dependencies;
  3. NSOperation的优先级

    • NSOperation提供了queuePriority属性,该属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。
    • 默认情况下新创建的操作对象优先级都是NSOperationQueuePriorityNormal
    • 可以通过 setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。
    1
    2
    3
    4
    5
    6
    7
    8
    //优先级枚举值
    typedef NS_ENUM(NSInteger,NSOperationQueuePriority){
    NSOperationQueuePriorityLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriotityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
    };
  4. 操作的状态和执行顺序

    NSOperation的状态有:ready(准备就绪)/ isExecuting(正在执行)/ isCancelled(已取消)/ isFinished`(已完成)

    • 对于添加到队列中的操作,是否进入就绪状态取决于操作之间的依赖关系

    • 当一个操作的所有依赖都已经完成时,操作对象通常会进入准备就绪状态,等待执行。

    • 进入就绪状态的操作的开始执行顺序由操作之间相对的优先级(queuePriority属性)决定。

    queuePriority 属性决定了进入准备就绪状态下的操作之间的开始执行顺序:

    • 如果一个队列中既有高优先级操作,又有低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。
    • 如果一个队列中既有ready状态的操作,又有未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高,那么,虽然准备就绪的操作优先级低,也会优先执行。
  5. 队列的暂停和取消

    • 队列的暂停和取消,并不是将当前的操作立即暂停或取消,而是在当前操作执行完毕之后不再执行新的操作
    • 暂停之后还可以恢复,继续向下执行;
    • 取消之后,所有的操作就清空了,无法继续执行剩下的操作。
  6. 操作的取消

    • 取消操作对象会将该对象保留在队列中,但会通知该对象它应尽快停止其任务。
    • 对于当前正在执行的操作,这意味着操作对象的工作代码必须检查取消状态,停止正在执行的操作,并将自身标记为完成。
    • 对于已排队但尚未执行的操作,队列必须仍然调用操作对象的start()方法,以便它可以处理取消事件并将其自身标记为finished

    取消操作会导致该操作忽略其可能具有的任何依赖关系。此行为使队列有可能尽快执行操作的start()方法。该start()方法又将操作移至完成状态,以便可以将其从队列中删除。

在多线程中使用单个的operationQueue对象是线程安全的。不需要加锁来同步操作。

Swift中,OperationQueue使用Dispatch框架来启动其操作的执行。无论操作被指定为同步还是异步,操作始终在单独的线程上执行。