NSOperation 高階用法之NSOperation基礎(NSOperation原始碼分析)(上)
這個文章是依照 WWDC 2015 Advanced NSOperations 而來的,主要講解Operation(OC:NSOperation)的高階用法。
本篇主要講NSOperation的基礎知識和NSOperation原始碼分析(Swift版)
注:Swift的Operation、OperationQueue對應OC中的NSOperation、NSOperationQueue
目錄如下:
- NSOperation 基礎
- NSOperationQueue 如何管理 NSOperation
- NSOperationQueue排程的原理
- _concurrencyGate、_underlyingQueue 和 queueGroup
- _runOperation:任務執行的核心
- Operation依賴機制的實現原理
NSOperation 基礎
NSOperation 是iOS Foundation中描述任務(task)的抽象類。NSOperation自帶強大的狀態機,有Pending、 Ready、 Executing、Finished、Cancelled。
通常我們需要繼承NSOperation來滿足我們需求。NSBlockOperation是官方實現好的子類,簡單使用如下:
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op2");
}];
//1.
[op1 addDependency:op2];
//2.
[[NSOperationQueue mainQueue] addOperation:op1];
[[NSOperationQueue mainQueue] addOperation:op2];
以上程式將依次輸出 op2 op1
是NSOperation的依賴機制,op1將依賴op2,也就是說op2執行結束後緊跟著執行op1
談NSOperation就離不開NSOperationQueue,NSOperationQueue 是管理NSOperation 的佇列,加入佇列(queue)中的任務的管理權就交給NSOperationQueue了。
那麼問題來了,NSOperationQueue是怎麼管理NSOperation的呢?
NSOperationQueue 如何管理 NSOperation
NSOperation 有5種狀態
5種狀態轉換如圖。除了Finished其他狀態都可以進入Cancelled。
假設佇列中有多個任務,Pending 表示即將進入Ready狀態,第一個進入Ready狀態的任務已經做好準備可以進入Executing狀態,任務執行完畢後會從 Executing狀態進入Finished,接著任務就會從佇列中移除。
綠色任務進入Ready狀態
任務執行完畢後從佇列中移除
NSOperation的依賴機制是當op2進入Finished狀態,依賴於op2的op1進入Ready狀態準備開始執行。
由此很清楚了,NOperationQueue是得知任務當前狀態的改變來實現任務的排程的,那麼Foundation內部是如何實現的呢?
NSOperationQueue排程的原理
從呼叫addOperation開始,封裝成陣列交給addOperations,任務排程權就交給了operationQueue。執行任務和任務之間的依賴處理的主要方法就是_runOperation。
open func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) {
#if DEPLOYMENT_ENABLE_LIBDISPATCH
var waitGroup: DispatchGroup?
if wait {
waitGroup = DispatchGroup()
}
#endif
/*
If QueuePriority was not supported this could be much faster
since it would not need to have the extra book-keeping for managing a priority
queue. However this implementation attempts to be similar to the specification.
As a concequence this means that the dequeue may NOT nessicarly be the same as
the enqueued operation in this callout. So once the dispatch_block is created
the operation must NOT be touched; since it has nothing to do with the actual
execution. The only differential is that the block enqueued to dispatch_async
is balanced with the number of Operations enqueued to the OperationQueue.
*/
lock.lock()
ops.forEach { (operation: Operation) -> Void in
operation._queue = self
//呼叫_operations的insert就是按任務的優先順序,放入不同的陣列中。_operations型別為_OperationList,控制著任務的優先順序。
_operations.insert(operation)
}
lock.unlock()
ops.forEach { (operation: Operation) -> Void in
#if DEPLOYMENT_ENABLE_LIBDISPATCH
if let group = waitGroup {
group.enter()
}
//將Operation封裝進block,與queueGroup關聯,放到_underlyingQueue中執行。
let block = DispatchWorkItem(flags: .enforceQoS) { () -> Void in
if let sema = self._concurrencyGate {
sema.wait()
self._runOperation()
sema.signal()
} else {
self._runOperation()
}
if let group = waitGroup {
group.leave()
}
}
_underlyingQueue.async(group: queueGroup, execute: block)
#endif
}
#if DEPLOYMENT_ENABLE_LIBDISPATCH
if let group = waitGroup {
group.wait()
}
#endif
}
_operations 是型別為_OperationList的結構體,內部有多個陣列,分別對應著不同的優先順序。
var veryLow = [Operation]()
var low = [Operation]()
var normal = [Operation]()
var high = [Operation]()
var veryHigh = [Operation]()
var all = [Operation]()
insert方法的作用就是按任務的優先順序,放入不同的任務優先順序陣列中。與insert相對應的dequeue是按照優先順序由高到低從陣列中取出任務。在接下來要介紹的_runOperation方法中將會用到dequeue來取出任務執行。
mutating func dequeue() -> Operation? {
if !veryHigh.isEmpty {
return veryHigh.remove(at: 0)
}
if !high.isEmpty {
return high.remove(at: 0)
}
if !normal.isEmpty {
return normal.remove(at: 0)
}
if !low.isEmpty {
return low.remove(at: 0)
}
if !veryLow.isEmpty {
return veryLow.remove(at: 0)
}
return nil
}
_runOperation是任務執行的核心,那麼OperationQueue到底是怎麼排程Operation的呢?在介紹_runOperation之前,我們來看看什麼時候呼叫_runOperation。
_concurrencyGate、_underlyingQueue 和 queueGroup
_concurrencyGate 控制併發執行幾個任務的訊號量,可以併發的數量就是我們maxConcurrentOperationCount的值。_runOperation的執行受_concurrencyGate訊號量控制。wait()訊號量減一,signal()訊號量加一,當訊號量為0時,就會一直等待,直接大於0時才會正常執行。
將由訊號量控制的_runOperation封裝進block,這個block與queueGroup關聯,放到佇列中非同步執行。執行_runOperation之前訊號量執行一次wait,_runOperation執行完畢之後執行一次signal,確保同時執行的任務數量滿足maxConcurrentOperationCount設定的值
總結:新增至OperationQueue物件中的所有的任務都跟queueGroup關聯,並且是放到_underlyingQueue佇列中執行的。
let block = DispatchWorkItem(flags: .enforceQoS) { () -> Void in
if let sema = self._concurrencyGate {
sema.wait()
self._runOperation()
sema.signal()
} else {
self._runOperation()
}
if let group = waitGroup {
group.leave()
}
}
_underlyingQueue.async(group: queueGroup, execute: block)
哦,其實排程Operation的關鍵又多了兩個:_underlyingQueue和queueGroup。
queueGroup的意義只有一個就是waitUntilAlloperationsAreFinished的實現,
DispatchGroup的wait函式會阻塞當前執行緒,直到所有的任務都執行完畢。
open func waitUntilAllOperationsAreFinished() {
#if DEPLOYMENT_ENABLE_LIBDISPATCH
queueGroup.wait()
#endif
}
再看_underlyingQueue變數,它的作用是為了獲取__underlyingQueue變數,如果__underlyingQueue存在就直接返回,如果不存在就生成一個queue。
如果是通過OperationQueue的main方法初始化OperationQueue,會走到OperationQueue內部的init(_queue queue: DispatchQueue, maxConcurrentOperations: Int = OperationQueue.defaultMaxConcurrentOperationCount)方法,__underlyingQueue就會被賦值;對於直接呼叫init方法生成的初始化的OperationQueue,__underlyingQueue是沒有賦值的,在呼叫_underlyingQueue的時候重新建立__underlyingQueue。
程式碼邏輯如下:
private static let _main = OperationQueue(_queue: .main, maxConcurrentOperations: 1)
open class var main: OperationQueue {
return _main
}
internal init(_queue queue: DispatchQueue, maxConcurrentOperations: Int = OperationQueue.defaultMaxConcurrentOperationCount) {
__underlyingQueue = queue
maxConcurrentOperationCount = maxConcurrentOperations
super.init()
queue.setSpecific(key: OperationQueue.OperationQueueKey, value: Unmanaged.passUnretained(self))
}
internal var _underlyingQueue: DispatchQueue {
lock.lock()
if let queue = __underlyingQueue {
lock.unlock()
return queue
} else {
let effectiveName: String
if let requestedName = _name {
effectiveName = requestedName
} else {
effectiveName = "NSOperationQueue::\(Unmanaged.passUnretained(self).toOpaque())"
}
let attr: DispatchQueue.Attributes
if maxConcurrentOperationCount == 1 {
attr = []
__concurrencyGate = DispatchSemaphore(value: 1)
} else {
attr = .concurrent
if maxConcurrentOperationCount != OperationQueue.defaultMaxConcurrentOperationCount {
__concurrencyGate = DispatchSemaphore(value:maxConcurrentOperationCount)
}
}
let queue = DispatchQueue(label: effectiveName, attributes: attr)
if _suspended {
queue.suspend()
}
__underlyingQueue = queue
lock.unlock()
return queue
}
}
_runOperation:任務執行的核心
再看_runOperation方法中的_dequeueOperation方法就是前文介紹的:將Operation物件從_operations中取出,最終執行Operation物件的start方法。
_waitUntilReady的方法也是利用的DispatchGroup的wait函式阻塞執行緒,等到group中的所有的任務都執行完畢。順便介紹OperationQueue是如何管理任務之間的依賴的。
internal func _runOperation() {
if let op = _dequeueOperation() {
if !op.isCancelled {
op._waitUntilReady()
if !op.isCancelled {
op.start()
}
}
}
}
//class Operation
internal func _waitUntilReady() {
_depGroup.wait()
_ready = true
}
}
Operation依賴機制的實現原理
前置知識:DispatchGroup的enter和wait函式必須要搭配使用(看文章引用)。
我們的目的是要op1依賴於op2執行完畢後再執行。
addDependency方法會將op1的_depGroup加入到op._groups陣列中,同時進入_depGroup。那什麼時候leave呢?答案就在Operation的finish方法。finish方法是Operation在執行結束時呼叫, 而其中的_leaveGroups方法會呼叫_groups所有的DispatchGroup物件的leave函式,所以_depGroup也將全部執行完畢,_depGroup.wait()之後的程式碼得以順利執行。
總結:op2執行完畢之後會遍歷_groups,同時呼叫leave()。這個時候op1的_depGroup執行完畢,wait()不再等待,op1的start方法開始執行。
op1.addDependency(op2)
//class Operation
open func addDependency(_ op: Operation) {
lock.lock()
_dependencies.insert(op)
op.lock.lock()
_depGroup.enter()
op._groups.append(_depGroup)
op.lock.unlock()
lock.unlock()
}
internal func finish() {
lock.lock()
_finished = true
_leaveGroups()
lock.unlock()
if let queue = _queue {
queue._operationFinished(self)
}
#if DEPLOYMENT_ENABLE_LIBDISPATCH
// The completion block property is a bit cagey and can not be executed locally on the queue due to thread exhaust potentials.
// This sets up for some strange behavior of finishing operations since the handler will be executed on a different queue
if let completion = completionBlock {
DispatchQueue.global(qos: .background).async { () -> Void in
completion()
}
}
#endif
}
internal func _leaveGroups() {
// assumes lock is taken
#if DEPLOYMENT_ENABLE_LIBDISPATCH
_groups.forEach() { $0.leave() }
_groups.removeAll()
_group.leave()
#endif
}
如有錯誤,歡迎斧正=^=
相關引用
Foundation/Operation.swift
WWDC 2015 Advanced NSOperations
細說GCD(Grand Central Dispatch)如何用
相關文章
- 多執行緒之 NSOperation 基礎用法執行緒
- 多執行緒之NSOperation執行緒
- iOS 多執行緒之NSOperationiOS執行緒
- NSOperation, NSOperationQueue 原理探析
- iOS NSOperation的基本使用iOS
- iOS - NSOperation與NSOperationQueueiOS
- GCD與NSOperation之間的區別GC
- NSOperation的進階使用和簡單探討
- GCD NSOperation 執行緒安全GC執行緒
- 執行緒9--NSOperation執行緒
- 串聯NSOperation知識點
- iOS多執行緒NSOperation篇iOS執行緒
- 還在用GCD?來看看 NSOperation 吧GC
- iOS多執行緒:NSOperation詳解iOS執行緒
- 執行緒10--NSOperation的基本操作執行緒
- iOS多執行緒(Pthread、NSThread、GCD、NSOperation)iOS執行緒threadGC
- 多執行緒-NSOperation中使用ASIHttpRequest注意事項執行緒HTTP
- iOS多執行緒程式設計之NSOperation和NSOperationQueue的使用iOS執行緒程式設計
- 玩轉iOS開發:iOS中的NSOperation開發(一)iOS
- iOS 多執行緒:『NSOperation、NSOperationQueue』詳盡總結iOS執行緒
- iOS多執行緒:『NSOperation、NSOperationQueue』詳盡總結iOS執行緒
- 使用NSOperation和NSURLSession封裝一個序列下載器Session封裝
- 玩轉iOS開發:iOS中的NSOperation開發(二)iOS
- ios多執行緒程式設計(NSThread)(NSOperation )(GCD)iOS執行緒程式設計threadGC
- iOS多執行緒程式設計——GCD與NSOperation總結iOS執行緒程式設計GC
- iOS多執行緒程式設計技術之NSThread、Cocoa NSOperation、GCDiOS執行緒程式設計threadGC
- NSOperation的多執行緒使用以及和GCD的對比執行緒GC
- Redux 高階 -- 原始碼分析Redux原始碼
- iOS併發機制(三) —— NSOperation實現多併發之建立佇列和開啟執行緒iOS佇列執行緒
- Swoole 原始碼分析——基礎模組之 Pipe 管道原始碼
- java基礎:ArrayList — 原始碼分析Java原始碼
- java基礎:HashMap — 原始碼分析JavaHashMap原始碼
- java基礎:Enum — 原始碼分析Java原始碼
- java基礎:Integer — 原始碼分析Java原始碼
- java基礎:TreeMap — 原始碼分析Java原始碼
- Java基礎——HashMap原始碼分析JavaHashMap原始碼
- Java集合原始碼分析之基礎(二):雜湊表Java原始碼
- java基礎:LinkedList — 原始碼分析Java原始碼