iOS多執行緒程式設計總結(中)

bestswifter發表於2018-01-03

#主要內容

經過上一章的學習,我們已經理解了多執行緒程式設計的基本概念,以及GCD的簡單使用。在這一章中將會介紹和NSOperation和和NSOperationQueue。主要涉及這幾個方面:

  1. NSOperationNSOperationQueue用法介紹
  2. NSOperation的暫停、恢復和取消
  3. 通過KVO對NSOperation的狀態進行檢測
  4. 多個NSOperation的之間的依賴關係

#NSOperation

從簡單意義上來說,NSOperation是對GCD中的block進行的封裝,它也表示一個要被執行的任務。

與GCD中的block類似,NSOperation物件有一個start()方法表示開始執行這個任務。

不僅如此,NSOperation表示的任務還可以被取消。它還有三種狀態isExecutedisFinishedisCancelled以方便我們通過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無法實現(或者至少說很難實現)的特性。由於NSOperationNSOperationQueue良好的封裝,這些新特性的使用都非常簡單。

###取消任務 如果我們有兩次網路請求,第二次請求會用到第一次的資料。如果此時網路情況不好,第一次請求超時了,那麼第二次請求也沒有必要傳送了。當然,使用者也有可能人為地取消某個NSOperation

當某個NSOperation被取消時,我們應該儘可能的清除NSOperation內部的資料並且把cancelledfinished設為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如何選擇

其實經過這兩篇文章的分析,我們大概對NSOperationGCD都有了比較詳細的瞭解,同時在親自運用這兩者的過程中有了自己的理解。

GCD以block為單位,程式碼簡潔。同時GCD中的佇列、組、訊號量、source、barriers都是組成並行程式設計的基本原語。對於一次性的計算,或是僅僅為了加快現有方法的執行速度,選擇輕量化的GCD就更加方便。

NSOperation可以用來規劃一組任務之間的依賴關係,設定它們的優先順序,任務能被取消。佇列可以暫停、恢復。NSOperation還可以被子類化。這些都是GCD所不具備的。

所以我們要記住的是:

NSOperation和GCD並不是互斥的,有效地結合兩者可以開發出更棒的應用

#總結

到目前為止,我們已經理解了多執行緒程式設計的基本概念,GCD的簡單使用,用NSOperation實現GCD的功能,以及NSOperation的高階特性。在最後一章iOS多執行緒程式設計總結(下)中會介紹GCD的底層特性,如barrier、suspend、resume、semaphore等。

相關文章