#主要內容
經過上一章的學習,我們已經理解了多執行緒程式設計的基本概念,以及GCD的簡單使用。在這一章中將會介紹和NSOperation
和和NSOperationQueue
。主要涉及這幾個方面:
NSOperation
和NSOperationQueue
用法介紹NSOperation
的暫停、恢復和取消- 通過KVO對
NSOperation
的狀態進行檢測 - 多個
NSOperation
的之間的依賴關係
#NSOperation
從簡單意義上來說,NSOperation
是對GCD中的block進行的封裝,它也表示一個要被執行的任務。
與GCD中的block類似,NSOperation
物件有一個start()
方法表示開始執行這個任務。
不僅如此,NSOperation
表示的任務還可以被取消。它還有三種狀態isExecuted
、isFinished
和isCancelled
以方便我們通過KVC對它的狀態進行監聽。
想要開始執行一個任務可以這麼寫:
let operation = NSBlockOperation { () -> Void in
print(NSThread.currentThread())
}
operation.addExecutionBlock { () -> Void in
print("execution block1 -- \(NSThread.currentThread())")
}
operation.start()
複製程式碼
以上程式碼會得到這樣的執行結果:
<NSThread: 0x7f89b1c070f0>{number = 1, name = main}
execution block1 -- <NSThread: 0x7f89b1e17030>{number = 2, name = (null)}
複製程式碼
首先我們建立了一個NSBlockOperation
,並且設定好它的block,也就是將要執行的任務。這個任務會在主執行緒中執行。
用NSBlockOperation
是因為NSOperation
是一個基類,不應該直接生成NSOperation
物件,而是應該用它的子類。NSBlockOperation
是蘋果預定義的子類,它可以用來封裝一個或多個block,後面會介紹如何自己建立NSOperation
的子類。
同時,還可以呼叫addExecutionBlock
方法追加幾個任務,這些任務會並行執行(也就是說很有可能執行在別的執行緒裡)。
最後,呼叫start
方法讓NSOperation
方法執行起來。start
是一個同步方法。
#NSOperationQueue
剛剛我們知道,預設的NSOperation
是同步執行的。簡單的看一下NSOperation
類的定義會發現它有一個只讀屬性asynchronous
這意味著如果想要非同步執行,就需要自定義NSOperation
的子類。或者使用NSOperationQueue
NSOperationQueue
類似於GCD中的佇列。我們知道GCD中的佇列有三種:主佇列、序列佇列和並行佇列。NSOperationQueue
更簡單,只有兩種:主佇列和非主佇列。
我們自己生成的NSOperationQueue
物件都是非主佇列,主佇列可以用NSOperationQueue.mainQueue
取得。
NSOperationQueue
的主佇列是序列佇列,而且其中所有NSOperation
都會在主執行緒中執行。
對於非主佇列來說,一旦一個NSOperation
被放入其中,那這個NSOperation
一定是併發執行的。因為NSOperationQueue
會為每一個NSOperation
建立執行緒並呼叫它的start
方法。
NSOperationQueue
有一個屬性叫maxConcurrentOperationCount
,它表示最多支援多少個NSOperation
併發執行。如果maxConcurrentOperationCount
被設為1,就以為這個佇列是序列佇列。
因此,NSOperationQueue
和GCD中的佇列有這樣的對應關係:
NSOperationQueue | GCD | |
---|---|---|
主佇列 | NSOperationQueue.mainQueue | dispatch_get_main_queue() |
序列佇列 | 自建佇列maxConcurrentOperationCount為1 | dispatch_queue_create("", DISPATCH_QUEUE_SERIAL) |
併發佇列 | 自建佇列maxConcurrentOperationCount大於1 | dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) |
回到開頭的問題,如何利用NSOperationQueue
實現非同步操作呢,程式碼如下:
let operationQueue = NSOperationQueue()
let operation = NSBlockOperation ()
operation.addExecutionBlock { () -> Void in
print("exec block1 -- \(NSThread.currentThread())")
}
operation.addExecutionBlock { () -> Void in
print("exec block2 -- \(NSThread.currentThread())")
}
operation.addExecutionBlock { () -> Void in
print("exec block3 -- \(NSThread.currentThread())")
}
operationQueue.addOperation(operation)
print("操作結束")
複製程式碼
得到執行結果如下:
操作結束
exec block1 -- <NSThread: 0x125672f10>{number = 2, name = (null)}
exec block2 -- <NSThread: 0x12556ba40>{number = 3, name = (null)}
exec block3 -- <NSThread: 0x125672f10>{number = 2, name = (null)}
複製程式碼
使用NSOperationQueue
來執行任務與之前的區別在於,首先建立一個非主佇列。然後用addOperation
方法替換之前的start
方法。剛剛已經說過,NSOperationQueue
會為每一個NSOperation
建立執行緒並呼叫他們的start
方法。
觀察一下執行結果,所有的NSOperation
都沒有在主執行緒執行,從而成功的實現了非同步、並行處理。
#NSOperation新特性
在學習NSOperation
的時候,我們總是用GCD的概念去解釋。但是NSOperation
作為對GCD更高層次的封裝,它有著一些GCD無法實現(或者至少說很難實現)的特性。由於NSOperation
和NSOperationQueue
良好的封裝,這些新特性的使用都非常簡單。
###取消任務
如果我們有兩次網路請求,第二次請求會用到第一次的資料。如果此時網路情況不好,第一次請求超時了,那麼第二次請求也沒有必要傳送了。當然,使用者也有可能人為地取消某個NSOperation
。
當某個NSOperation
被取消時,我們應該儘可能的清除NSOperation
內部的資料並且把cancelled
和finished
設為true
,把executing
設為false
。
//取消某個NSOperation
operation1.cancel()
//取消某個NSOperationQueue剩餘的NSOperation
queue.cencelAllOperations()
複製程式碼
###設定依賴
依然考慮剛剛所說的兩次網路請求的例子。因為第二次請求會用到第一次的資料,所以我們要保證發出第二次請求的時候第一個請求已經執行完。但是我們同時還希望利用到NSOperationQueue
的併發特性(因為可能不止這兩個任務)。
這時候我們可以設定NSOperation
之間的依賴關係。語法非常簡潔:
operation2.addDependency(operation1)
複製程式碼
需要注意的是NSOperation
之間的相互依賴會導致死鎖
###NSOperationQueue的暫停與恢復
這個更加簡單,只要修改suspended
屬性即可
queue.suspended = true //暫停queue中所有operation
queue.suspended = false //恢復queue中所有operation
複製程式碼
###NSOperation的優先順序
GCD中,任務(block)是沒有優先順序的,而佇列具有優先順序。和GCD相反,我們一般考慮NSOperation
的優先順序
NSOperation
有一個NSOperationQueuePriority
列舉型別的屬性queuePriority
public enum NSOperationQueuePriority : Int {
case VeryLow
case Low
case Normal
case High
case VeryHigh
}
複製程式碼
需要注意的是,NSOperationQueue
也不能完全保證優先順序高的任務一定先執行。
#NSOperation和GCD如何選擇
其實經過這兩篇文章的分析,我們大概對NSOperation
和GCD
都有了比較詳細的瞭解,同時在親自運用這兩者的過程中有了自己的理解。
GCD以block為單位,程式碼簡潔。同時GCD中的佇列、組、訊號量、source、barriers都是組成並行程式設計的基本原語。對於一次性的計算,或是僅僅為了加快現有方法的執行速度,選擇輕量化的GCD就更加方便。
而NSOperation
可以用來規劃一組任務之間的依賴關係,設定它們的優先順序,任務能被取消。佇列可以暫停、恢復。NSOperation
還可以被子類化。這些都是GCD所不具備的。
所以我們要記住的是:
NSOperation
和GCD並不是互斥的,有效地結合兩者可以開發出更棒的應用
#總結
到目前為止,我們已經理解了多執行緒程式設計的基本概念,GCD的簡單使用,用NSOperation
實現GCD的功能,以及NSOperation
的高階特性。在最後一章iOS多執行緒程式設計總結(下)中會介紹GCD的底層特性,如barrier、suspend、resume、semaphore等。