iOS開發之多執行緒程式設計總結(三)

發表於2016-11-12

前言

前段時間的心病落下帷幕後,一大波需求向我迎來,忙的我最近沒時間更新部落格了,只能在閒暇的時間吹吹牛逼了。這篇部落格主要講解NSOperation的一些知識。

11970779-b26f98e24a80adb1
busy Time.jpg

1. NSOperation簡介

NSOperation是蘋果提供給我們的一套多執行緒解決方案。實際上NSOperation是基於GCD更高一層的封裝,但是比GCD更簡單易用、程式碼可讀性也更高。

  • GCD :則是一種更輕量級的,是基於C語言實現的,以 FIFO 的順序執行併發任務的方式,使用 GCD 時我們並不關心任務的排程情況,而讓系統幫我們自動處理。但是 GCD 的短板也是非常明顯的,比如我們想要給任務之間新增依賴關係、取消或者暫停一個正在執行的任務時就會變得非常棘手。
  • Operation Queues :相對 GCD 來說,使用 Operation Queues 會增加一點點額外的開銷,但是我們卻換來了非常強大的靈活性和功能,我們可以給 operation 之間新增依賴關係、取消一個正在執行的 operation 、暫停和恢復 operation queue 等。

因為NSOperation是基於GCD的,那麼使用起來也和GCD差不多,其中,NSOperation相當於GCD中的任務,而NSOperationQueue則相當於GCD中的佇列。NSOperation實現多執行緒的使用步驟分為三步:

  • 1.建立任務:先將需要執行的操作封裝到一個NSOperation物件中。
  • 2.建立佇列:建立NSOperationQueue物件。
  • 3.將任務加入到佇列中:然後將NSOperation物件新增到NSOperationQueue中。

之後,系統就會自動將NSOperationQueue中的NSOperation取出來,在新執行緒中執行操作。

下面就跟我一起學習NSOperation的相關知識點。

2. NSOperation和NSOperationQueue的基本使用

1. 建立任務

在預設情況下,NSOperation 是同步執行的,也就是說會阻塞當前執行緒直到任務完成。

NSOperation 本身是一個抽象類,不能直接例項化,因此,如果我們想要使用它來執行具體任務的話,就必須使用系統預定義的兩個子類NSInvocationOperationNSBlockOperation或者建立自己的子類

  • 1.使用子類NSInvocationOperation
  • 2.使用子類NSBlockOperation
  • 3.定義繼承自NSOperation的子類,通過實現內部相應的方法來建立任務。

1.使用子類NSInvocationOperation

NSInvocationOperation:我們可以通過一個 object 和 selector 非常方便地建立一個 NSInvocationOperation ,這是一種非常動態和靈活的方式。

NSInvocationOperation方法:

NSInvocationOperation-Demo:

2.使用子類NSBlockOperation

NSBlockOperation:我們可以使用 NSBlockOperation 來併發執行一個或多個 block ,只有當一個 NSBlockOperation 所關聯的所有 block 都執行完畢時(會阻塞當前執行緒),這個 NSBlockOperation 才算執行完成,有點類似於 dispatch_group 的概念。

NSBlockOperation方法:

NSBlockOperation-Demo:

列印結果:

NSBlockOperation注意點:

  • 從上面列印結果看到在多個執行緒執行任務。addExecutionBlock:可以為NSBlockOperation新增額外的操作。如果當前NSOperation的任務只有一個的話,那肯定不會開闢一個新的執行緒,只能同步執行。只有NSOperation的任務數>1的時候,這些額外的操作才有可能在其他執行緒併發執行。注意我的用詞 “才有可能”,也就是說額外的操作也有可能在當前執行緒裡執行。

3. 定義繼承自NSOperation的子類

NSOperation的子類:當系統預定義的兩個子類 NSInvocationOperationNSBlockOperation 不能很好的滿足我們的需求時,我們可以自定義自己的 NSOperation 子類,新增我們想要的功能。我們可以自定義非併發和併發兩種不同型別的 NSOperation 子類,而自定義一個前者要比後者簡單得多。我們先來一個簡單的非併發的NSOperation 子類,併發的NSOperation的單獨在後面講解!

非併發的NSOperation 子類Demo:

先定義一個繼承自NSOperation的子類,重寫main方法
JYSerialOperation.h

JYSerialOperation.m

使用的時候匯入標頭檔案然後呼叫:

2. 建立佇列

和GCD中的併發佇列、序列佇列略有不同的是:NSOperationQueue一共有兩種佇列:主佇列、其他佇列。其中其他佇列同時包含了序列、併發功能。下邊是主佇列、其他佇列的基本建立方法和特點。

主佇列

  • 凡是新增到主佇列中的任務(NSOperation),都會放到主執行緒中執行

其他佇列(非主佇列)

  • 新增到這種佇列中的任務(NSOperation),就會自動放到子執行緒中執行
  • 同時包含了:序列、併發功能

3. 把任務加入到佇列中

只要將任務加入到佇列中,就不要執行start方法,佇列會負責排程任務自動執行start方法。加入佇列的方法如下:

1.- (void)addOperation:(NSOperation *)op;

  • 首先建立任務operation,然後將建立好的任務新增佇列!

  • 列印結果:

  • 從上面可以看到NSOperation Queue會開闢執行緒。然後併發執行!

2.- (void)addOperationWithBlock:(void (^)(void))block ;

  • 無需先建立任務,在block中新增任務,直接將任務block加入到佇列中。

  • 列印結果:

可以看出addOperationWithBlock:和NSOperationQueue能夠開啟新執行緒,進行併發執行。

3. 佇列的重要屬性maxConcurrentOperationCount

maxConcurrentOperationCount佇列的最大併發數,也就是當前執行佇列的任務時,最多開闢多少條執行緒!具體開多少條執行緒是由底層執行緒池來決定。

佇列是序列還是併發就是由maxConcurrentOperationCount來決定

  • maxConcurrentOperationCount預設情況下為-1,表示不進行限制,預設為併發執行。
  • maxConcurrentOperationCount為1時,進行序列執行。
  • maxConcurrentOperationCount大於1時,進行併發執行,當然這個值不應超過系統限制,即使自己設定一個很大的值,系統也會自動調整。

程式碼參考上一個- (void)addOperationWithBlock:(void (^)(void))block ;Demo,修改最大併發數即可測試,結果如下:

注意點:

  • maxConcurrentOperationCount設定為1時,是序列佇列,也有可能開闢多條執行緒。序列只是一種執行任務的方式,跟開闢執行緒是不同緯度的概念別弄混了,同步和非同步決定開不開執行緒,可以參考上一篇部落格iOS開發之多執行緒程式設計總結(二)的基本概念。
  • maxConcurrentOperationCount設定為1時,是序列佇列,但是 operation 的執行順序還是一樣會受其他因素影響的,比如 operation 的 isReady 狀態、operation 的佇列優先順序等,如果operation 的執行順序對我們來說非常重要,那麼我們就應該在將 operation 新增到 operation queue 之前就建立好它的依賴關係。

4. 任務的操作依賴

通過配置依賴關係,我們可以讓不同的 operation 序列執行,正如我們上面剛剛提到的最大併發數為1時序列執行(但是順序不一定會是我們想要的順序),一個 operation 只有在它依賴的所有 operation 都執行完成後才能開始執行。配置 operation 的依賴關係主要涉及到NSOperation 類中的以下兩個方法:

  • 特別注意1:addDependency:方法新增的依賴關係是單向的,比如 [A addDependency:B];,表示 A 依賴 B,B 並不依賴 A 。
  • 特別注意2:addDependency:可以跨佇列新增依賴,原因:上面新增依賴關係的方法是存在於 NSOperation 類中的,operation 的依賴關係是它自己管理的,與它被新增到哪個 operation queue 無關。
  • 特別注意3:千萬不要在 operation 之間新增迴圈依賴,這樣會導致這些 operation 都不會被執行。

任務的操作依賴Demo:

列印結果:

5. 其他方法介紹:

NSOperation方法:

NSOperation Queue方法:

補充知識點:

  • 取消任務:當一個 operation 開始執行後,它會一直執行它的任務直到完成或被取消為止。我們可以在任意時間點取消一個 operation ,甚至是在它還未開始執行之前。為了讓我們自定義的 operation 能夠支援取消事件,我們需要在程式碼中定期地檢查 isCancelled 方法的返回值,一旦檢查到這個方法返回 YES ,我們就需要立即停止執行接下來的任務。根據蘋果官方的說法,isCancelled 方法本身是足夠輕量的,所以就算是頻繁地呼叫它也不會給系統帶來太大的負擔。

    The isCancelled method itself is very lightweight and can be called frequently without any significant performance penalty.

    通常來說,當我們自定義一個 operation 類時,我們需要考慮在以下幾個關鍵點檢查 isCancelled 方法的返回值:

    • 在真正開始執行任務之前;
    • 至少在每次迴圈中檢查一次,而如果一次迴圈的時間本身就比較長的話,則需要檢查得更加頻繁;
    • 在任何相對來說比較容易中止 operation 的地方。

    看到這裡,我想你應該可以意識到一點,那就是儘管 operation 是支援取消操作的,但卻並不是立即取消的,而是在你呼叫了 operation 的 cancel 方法之後的下一個 isCancelled 的檢查點取消的。

  • 任務在佇列中的優先順序:對於被新增到 operation queue 中的 operation 來說,決定它們執行順序的第一要素是它們的 isReady 狀態,其次是它們在佇列中的優先順序。operation 的 isReady 狀態取決於它的依賴關係,而在佇列中的優先順序則是 operation 本身的屬性。預設情況下,所有新建立的 operation 的佇列優先順序都是 normal 的,但是我們可以根據需要通過setQueuePriority: 方法來提高或降低 operation 的佇列優先順序。
    • 優先順序只是大概的判斷,跟GCD中的全域性佇列功能相似,並不能依賴這個做嚴格的任務順序
    • 佇列優先順序只應用於相同 operation queue 中的 operation 之間,不同 operation queue 中的 operation 不受此影響
  • completionBlock:一個 operation 可以在它的主任務執行完成時回撥一個 completion block 。我們可以用 completion block 來執行一些主任務之外的工作。
    • 當一個 operation 被取消時,它的 completion block 仍然會執行,所以我們需要在真正執行程式碼前檢查一下 isCancelled 方法的返回值。
    • 我們也沒有辦法保證 completion block 被回撥時一定是在主執行緒,理論上它應該是與觸發 isFinished 的 KVO 通知所在的執行緒一致的,所以如果有必要的話我們可以在 completion block 中使用 GCD 來保證從主執行緒更新 UI 。
  • 暫停和恢復 Operation Queue:暫停執行 operation queue 並不能使正在執行的 operation 暫停執行,而只是簡單地暫停排程新的 operation 。另外,我們並不能單獨地暫停執行一個 operation ,除非直接 cancel 掉。

6. 併發的NSOperation

在上面建立自定義子類NSOperation任務的時候只是建立了序列的NSOperation子類,只要重寫main方法即可。現在我們就來看看如何實現併發的子類NSOperation。

NSOperation有三個狀態量isCancelled, isExecutingisFinished.

實現併發(concurrent)的NSOperation步驟:

  1. 重寫start()函式
    • 必須的,所有併發執行的 operation 都必須要重寫這個方法,替換掉 NSOperation 類中的預設實現。start 方法是一個 operation 的起點,我們可以在這裡配置任務執行的執行緒或者一些其它的執行環境。另外,需要特別注意的是,在我們重寫的 start 方法中一定不要呼叫父類的實現。
  2. 重寫main函式
    • 可選的,通常這個方法就是專門用來實現與該 operation 相關聯的任務的。儘管我們可以直接在 start 方法中執行我們的任務,但是用 main 方法來實現我們的任務可以使設定程式碼和任務程式碼得到分離,從而使 operation 的結構更清晰。
  3. isExecuting 和 isFinished
    • 必須的,併發執行的 operation 需要負責配置它們的執行環境,並且向外界客戶報告執行環境的狀態。因此,一個併發執行的 operation 必須要維護一些狀態資訊,用來記錄它的任務是否正在執行,是否已經完成執行等。此外,當這兩個方法所代表的值發生變化時,我們需要生成相應的 KVO 通知,以便外界能夠觀察到這些狀態的變化。
    • 在併發情況下系統不知道operation什麼時候finished, operation裡面的task一般來說是非同步執行的, 也就是start函式返回了operation不一定就是finish了, 這個你自己來控制, 你什麼時候將isFinished置為YES(傳送相應的KVO訊息), operation就什麼時候完成了。
  4. 重寫isConcurrent函式
    • 必須的,這個方法的返回值用來標識一個 operation 是否是併發的 operation ,我們需要重寫這個方法並返回 YES 。

併發的NSOperationDemo:

JYConcurrentOperation2.h

JYConcurrentOperation2.m

併發的使用:

併發NSOperation的Demo一些解釋:

程式碼中引入了RunLoop的東西->原因呢:

  • 我們把operation加到了非main queue(或者是在子執行緒呼叫的), 那麼問題來了, 你會發現NSURLConnection delegate不走了,
  • 主執行緒會自動建立一個RunLoop來保證程式一直執行. 但子執行緒預設不建立NSRunLoop, 所以子執行緒的任務一旦返回, 執行緒就over了.
  • 上面的併發operation當start函式返回後子執行緒就退出了, 當NSURLConnection的delegate回撥時, 執行緒已經木有了, 所以你也就收不到回撥了. 為了保證子執行緒持續live(等待connection回撥), 你需要在子執行緒中加入RunLoop, 來保證它不會被kill掉.

結尾:

今天的NSOperation就介紹到這裡,裡面有不對的地方希望大神們可以提出來,今天提到了RunLoop,大家可以學習一下相關的知識點。同時多執行緒的NSThread、GCD、NSOperation在這三篇文章中基本上介紹完了。

如果你喜歡請點喜歡,加關注哦^_^


ThreadDemo下載連結

iOS開發之多執行緒程式設計總結(一)
iOS開發之多執行緒程式設計總結(二)

參考資料:
http://www.cocoachina.com/ios/20150807/12911.html
http://www.jianshu.com/p/ebb3e42049fd

相關文章