Operation详细版

1,279 阅读6分钟

前言

​ iOS多线程主要库有GCD,Operation。网上也有很多介绍的文章。但更多只是停留在表面,API的使用。很少有文章能从源码角度分析Operation,OperationQueue。

本文的Operation源码来自苹果的Operation源码版本 2020_Feb_16 。版本最新。如果链接失效,可以去swift-corelibs-foundation里面找找。有时候版本更新,路径会发生改变。

​ 本文将以一问一答的形式为大家展示Operation源码细节。Let's go!

Operation 十问

Operation

同步Operation使用教程

重写main()方法即可。

源码:简单理解,OperationQueue调用Operation其实就调用了Op的start方法。

Op的start方法,做了些属性初始化,然后调用了main方法。

    internal func _schedule(_ op: Operation) {
        op._state = .starting
      
        // set current tsd
        OperationQueue._currentQueue.set(self)
        op.start()
        OperationQueue._currentQueue.clear()
        
        // unset current tsd
        if op.isFinished ... {
            Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: op)
        }
    }

题外话:

tsd: thread-local storage。线程特有数据。方便op的main函数执行时,知道自己处于哪个OperationQueue。

异步Operation使用教程

参考该Blog即可。

Asynchronous operations for writing concurrent solutions in Swift

总结:重写

  • isExecuting
  • isFinished
  • isAsynchronous
  • start()

为什么异步Operation这么使用

重写start()的必要性

源码start发生了什么?

1.start里面调用了main

2.运行完main,_state = finishing,发生属性变更信号。

⚠️不重写start,意味着自动调用start后,改op会自动finish。

我们的目标是异步Operation,执行完main,还要几秒才结束Operation。finishing信号需要我们自己控制。

open func start() {
        let state = _state
      
        let isCanc = _isCancelled
        if !isCanc {
		        _state = .executing
		        // _execute 函数里面就是调用了main()
            _queue?._execute(self) ?? main()
        }
        
        if __NSOperationState.executing == _state {
            _state = .finishing
            Operation.observeValue(forKeyPath: _NSOperationIsExecuting, ofObject: self)
            Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: self)
        } else {
            _state = .finishing
            Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: self)
        }
    }
    
open func main() { }

再看看源码的isExecuting,isFinished。

open var isExecuting: Bool {
    return __NSOperationState.executing == _state
}

open var isFinished: Bool {
    return __NSOperationState.finishing.rawValue <= _state.rawValue
}

结论:重写这些属性,是为了手动控制异步Operation的生命周期。而不是跑完main函数,op就finish了。finish属性很关键!后面就会提到。

Operations之间的依赖如何实现

_addDependency

这里引入两个数组。

  • __dependencies

  • __downDependencies

__dependencies

B.addDependency(A) | B.addDependency(A1) | B.addDependency(A2)

B依赖了3个Op,__dependencies就是[A2,A1,A],DependencyCount = 3.

_addParent

OperationsB 依赖 OperationsA

OperationsB1 依赖 OperationsA

OperationsB2 依赖 OperationsA

Parent就是往__downDependencies数组里面添加,A的__downDependencies数组是[B2,B1,A]。

总结:B.addDependency(A),把A添加进B依赖的数组。把B添加进A的被依赖数组。observeValue是发送ready信号,可能会触发queue的运行。

源码:

internal func _addDependency(_ op: Operation) {
    var up: Operation?

		// 把A添加进依赖数组。
    if __dependencies.first(where: { $0 === op }) == nil {
        __dependencies.append(op)
        up = op
    }
    
    // upwards此刻为A。
    if let upwards = up {
        let upIsFinished = upwards._state == __NSOperationState.finished
        if !upIsFinished && !_isCancelled {
            assert(_unfinishedDependencyCount >= 0)
            _incrementUnfinishedDependencyCount()
            upwards._addParent(self)
        }
    }
    
    Operation.observeValue(forKeyPath: _NSOperationIsReady, ofObject: self)
}

OperationsA执行完,运行OperationsB

OperationsA 运行完毕。

源码会有一句。包括我们自定义的异步Op。执行完毕都需要出发observeValue。

源码:
open func start() {
		main()
		_state = .finishing
    Operation.observeValue(forKeyPath: _NSOperationIsExecuting, ofObject: self)
    Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: self)
 }
    
自定义异步Op
 private var _isFinished: Bool = false
 override private(set) var isFinished: Bool {
     get {
         return lockQueue.sync { () -> Bool in
             return _isFinished
         }
     }
     set {
         willChangeValue(forKey: "isFinished")
         lockQueue.sync(flags: [.barrier]) {
             _isFinished = newValue
         }
         didChangeValue(forKey: "isFinished")
     }
 }

isFinished 触发了什么

B.addDependency(A) | B.addDependency(A1) | B.addDependency(A2)。

如果A,A1,A2都没有执行完毕,UnfinishedDependencyCount = 3.(UnfinishedDependencyCount是在之前依赖的时候进行添加的。上面有细节。

  • A完成了,UnfinishedDependencyCount-1 = 2.
  • A1完成了,UnfinishedDependencyCount-1 = 1.
  • A2完成了,UnfinishedDependencyCount 本来就是1,没有再等待的Op,ready去执行操作。

重点:A的isFinished减少那些依赖于它(比如B)的UnfinishedDependencyCount。遍历downDependencies数组,获取Op。

如果B的UnfinishedDependencyCount = 1,说明A是B的最后一个依赖,B触发observeValue,ready。ready意味着执行main函数。

barrier Operation

barrier Operation 调用时机的实现

比如OperationQueue里现在有op1-op10 10个Operation。此刻往里面添加barrier Operation。 barrier Operation 会addDependency (op1-op10)。这样,barrier Operation就可以保证在10个Operation都执行完毕后再执行了。

barrier Operation 是如何实现阻拦其他Operation的

这个和上面的实现方式没有区别,所有后加入的Op,Operation.addDependency(barrier Operation)。barrier Operation就能起到阻拦作用。

OperationQueue

Operations的优先级调度如何实现

OperationQueue的7条链表

首先说明OperationQueue有7条链表。

firstOperation,lastOperation存放所有被添加到OperationQueue中的Operation。(细节:不包括barrier Operation)

firstPriorityOperation,lastPriorityOperation存放不同优先级的链表的开头,和结尾。

var __firstOperation: Unmanaged<Operation>?
var __lastOperation: Unmanaged<Operation>?
var __firstPriorityOperation: (barrier: Unmanaged<Operation>?,
                               veryHigh: Unmanaged<Operation>?,
                               high: Unmanaged<Operation>?,
                               normal: Unmanaged<Operation>?,
                               low: Unmanaged<Operation>?,
                               veryLow: Unmanaged<Operation>?)
var __lastPriorityOperation: (barrier: Unmanaged<Operation>?,
                              veryHigh: Unmanaged<Operation>?,
                              high: Unmanaged<Operation>?,
                              normal: Unmanaged<Operation>?,
                              low: Unmanaged<Operation>?,
                              veryLow: Unmanaged<Operation>?)

调度

去掉大量的噪音。简化代码。

从高优先级开始遍历,获取该优先级的链表,执行链表的op。

这样,高优先级的就会比低优先级的先执行。

 internal func _schedule() {
        for prio in Operation.QueuePriority.priorities {
        		 var op = _firstPriorityOperation(prio) 
        		 while let operation = op? {
        	   			queue.async(execute: op.schedule)
        	   			op = next
        	   }
        }
}

OperationQueue如何实现并发量的控制

slotsAvail 和 _suspended决定是否继续调用Op。

如果已经达到最大并发,或者当前queue已经停止了。不在派发Op。

 internal func _schedule() {
 
    var slotsAvail(可并发数) = 最大并发数 - 正在执行的op数
    for prio in Operation.QueuePriority.priorities {
        var op = _firstPriorityOperation(prio) 
        while let operation = op? {
            if 0 >= slotsAvail || _suspended {
                break
            }
 	 
            queue.async(execute: op.schedule)
            op = next
        }
    }
}

OperationQueue,Operation用了哪些GCD的API

WorkItem

DispatchWorkItem:Operation的start都会被封装进WorkItem

其实OperationQueue调用Operation的方式是调用WorkItem。

 Operation属性__schedule
 var __schedule: DispatchWorkItem?
 
 DispatchQueue派发Operation操作。
 if let schedule = operation.__schedule {
       if operation is _BarrierOperation {
           queue.async(flags: .barrier, execute: {
               schedule.perform()
           })
       } else {
           queue.async(execute: schedule)
       }
}

封装WorkItem 的内部

封装的原因有两个:

  1. op调用start时,可以知道自己所处的queue。通过OperationQueue.current.(源码tsd处)
  2. 控制串型Op的finish信号
internal func _schedule(_ op: Operation) {
    op._state = .starting
    
    // set current tsd
    OperationQueue._currentQueue.set(self)
    op.start()
    OperationQueue._currentQueue.clear()
    
    if op.isFinished && op._state.rawValue ... {
        Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: op)
    }
}

DispatchQueue

用于派发任务,异步调用WorkItem。

 if let schedule = operation.__schedule {
       if operation is _BarrierOperation {
           queue.async(flags: .barrier, execute: {
               schedule.perform()
           })
       } else {
           queue.async(execute: schedule)
       }
}

waitUntilFinished 的使用和实现

OperationQueue func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) {
        _addOperations(ops, barrier: false)
    if wait {
        for op in ops {
            op.waitUntilFinished()
        }
    }
}
    
---

operation func waitUntilFinished() {
    __waitCondition.lock()
    while !isFinished {
        __waitCondition.wait()
    }
    __waitCondition.unlock()
}

waitUntilFinished = YES,就是让所有的Op进入wait()状态。也就是当前线程加了9个锁。

unlock:

observeValue 的finish中,isFinished = YES。所有Op执行完毕,当前线程可以继续运行。

		op.__waitCondition.lock()
 		op.__waitCondition.broadcast()
    op.__waitCondition.unlock()

OperationQueue 的内部任务派发机制

从Operation添加到被执行,中间发生了什么?

添加到队列

  • Operation依据Qos被封装成DispatchWorkItem,作为自己的属性,方便到时候的派发。
  • Operation依据Qos等级,被分配到对应优先级的链表。(前面提到过,不同优先级Op有不同优先级的链表)

执行

  • OperationQueue 并发分配任务,从优先级高的链表开始调度。
  • 没有超过最大并发数,异步执行被封装的DispatchWorkItem。

Blog

Asynchronous operations for writing concurrent solutions in Swift

WWDC

Advanced NSOperations

wwdc2015 226源码

源码版本

Operation源码版本 2020_Feb_16

个人修改注释版,直接把源码拷贝过来,改了名字。但运行起来会崩溃,没找到原因。但跑起来,也足够研究里面的90%逻辑了。记住,YJOperationQueue和YJOperation搭配使用。

个人修改注释版