NSOperation 和 NSOperationQueue

之前做 iOS 的并發(fā)編程的時(shí)候一直用的 GCD纯赎,NSOperation 用得很少,這個(gè)類對(duì)我來(lái)說(shuō)一直比較神秘飘千,于是最近仔細(xì)研究了一下染坯。注意這篇文章并不會(huì)很詳細(xì)講到各個(gè)方面羹应,只是解答一些剛開始接觸 NSOperation 時(shí)會(huì)遇到的疑惑掀鹅。

我們?cè)谑褂?GCD 的時(shí)候散休,通常會(huì)將 block dispatch 到其他線程異步執(zhí)行,就像這樣:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    // some task you want to perform asynchronously on other thread
}

這里的 block 就是我們希望異步執(zhí)行的代碼乐尊,這段代碼被當(dāng)做參數(shù)傳入 dispatch_async 函數(shù)戚丸,然后它為我們開啟一個(gè)新的線程,用 dispatch queue 來(lái)管理 block 的執(zhí)行順序扔嵌。

其實(shí) NSOperation 就是對(duì)我們需要異步執(zhí)行的代碼的面向?qū)ο蟮姆庋b限府,上面的代碼換作 NSOperation 會(huì)這么寫:

let queue = NSOperationQueue()
queue.addOperationWithBlock { 
    // some task you want to perform asynchronously on other thread
}

你會(huì)發(fā)現(xiàn)上面的代碼根本沒(méi)有出現(xiàn) NSOperation,而是出現(xiàn)了 NSOperationQueue痢缎,這是為什么呢胁勺?

因?yàn)榍懊嬉呀?jīng)說(shuō)過(guò),NSOperation 只是對(duì)執(zhí)行代碼的封裝独旷,本身并不提供任何的異步功能署穗,NSOperationQueue 底層是基于 GCD 的封裝,所以一般來(lái)說(shuō)嵌洼,我們會(huì)用 NSOperationQueue 來(lái)控制 operation 的執(zhí)行案疲,queue 會(huì)根據(jù) operation 的優(yōu)先級(jí)、依賴等來(lái)決定如何執(zhí)行添加進(jìn) queue 的 operation麻养。

之前的代碼我們用了 NSOperationQueue 的方法 addOpertionWithBlock 來(lái)給 queue 方便的添加 operation褐啡,接下來(lái)我們來(lái)使用 NSOperation,但是 NSOperation 是一個(gè)“抽象”類(注意這里的抽象是指它功能上的抽象鳖昌,OC 和 Swift 中并沒(méi)有真正的抽象類的概念)备畦,所以你只能通過(guò)它的子類來(lái)使用它低飒,系統(tǒng)已經(jīng)提供了兩個(gè),NSBlockOperation 和 NSInvocationOperation(Swift 不支持后者)萍恕,使用起來(lái)就像下面這樣:

let queue = NSOperationQueue()
let operation = NSBlockOperation {
    // ...
}
queue.addOperation(operation)

使用 NSOperation 的好處是逸嘀,你可以指定 operation 的優(yōu)先級(jí)和依賴车要,當(dāng)多個(gè) operaton 添加到 queue 時(shí)允粤,queue 可以根據(jù)這些來(lái)確定執(zhí)行順序。

當(dāng)然翼岁,你也可以自定義 NSOperation 的子類类垫,你可以選擇定義同步的子類或者異步的子類。系統(tǒng)提供的兩個(gè)子類都是同步的琅坡,你可以通過(guò)打印他們的 asynchronous 屬性來(lái)查看悉患。

為什么還需要定義異步的 NSOperation 子類呢?

之前沒(méi)有提到的是榆俺,NSOperation 不僅可以通過(guò)添加到 queue 里面執(zhí)行售躁,還可以通過(guò) start 方法執(zhí)行,就像這樣:

let operation = NSBlockOperation {
    // ...
}
operation.start()

對(duì)于上面這段代碼茴晋,這樣寫基本是沒(méi)有意義的陪捷,因?yàn)檫@段代碼還是會(huì)在主線程執(zhí)行,跟直接執(zhí)行 block 里面的代碼沒(méi)有區(qū)別诺擅。

通過(guò)官方文檔可以知道市袖,如果你只想要一個(gè)同步的 NSOperation 的子類,你只需要重寫 main 方法就行了烁涌,但是苍碟,要想讓它異步,你需要干的事情更多撮执。

如何實(shí)現(xiàn)我們待會(huì)再說(shuō)微峰,先再多說(shuō)一點(diǎn)關(guān)于 NSOperation 和 NSOperationQueue 的關(guān)系。

之前提到 queue 能控制 operation 的執(zhí)行順序抒钱,它還能控制它開啟的線程最多能執(zhí)行的 operation 的個(gè)數(shù)蜓肆。那么 queue 是如何知道 operation 是什么時(shí)候開始執(zhí)行,什么執(zhí)行完成继效,什么時(shí)候被取消的呢症杏?

這與 operation 維護(hù)的狀態(tài)機(jī)有關(guān),operation 有四個(gè)布爾屬性瑞信,它們的狀態(tài)可以用下面這張圖表示:

state.png

當(dāng)一個(gè) operation 的所有依賴都完成執(zhí)行后厉颤,它就會(huì)進(jìn)入 ready 狀態(tài),準(zhǔn)備開始執(zhí)行了凡简,但是如果此時(shí)它已經(jīng)被 cancel 了逼友,那么它不會(huì)開始執(zhí)行而是直接進(jìn)入 finished 狀態(tài)精肃,反之那么它將進(jìn)入 executing 狀態(tài),開始執(zhí)行帜乞,執(zhí)行結(jié)束后進(jìn)入 finished 狀態(tài)司抱。

這里多說(shuō)一下 NSOperation 的 cancel 方法,這個(gè)方法只會(huì)將它的 isCanceled 屬性變?yōu)?true黎烈,其他的什么也不會(huì)做习柠。也就是說(shuō),如果這個(gè) operation 已經(jīng)開始執(zhí)行了的話照棋,那么只有等到 operation 的代碼執(zhí)行結(jié)束后它的 state 才會(huì)變?yōu)?finished资溃。

queue 就是根據(jù)這些狀態(tài)量以及優(yōu)先級(jí)和其他因素來(lái)確定 operation 的執(zhí)行順序的。operation 用到了 iOS 中一個(gè)比較重要的黑科技 KVO 來(lái)通知 queue 自身狀態(tài)的改變烈炭,可以猜想的是溶锭,在 NSOperation 的默認(rèn)實(shí)現(xiàn)中,比如 start 方法中符隙,肯定含有不少的 KVO 代碼趴捅。

注意能被 KVO 觀察的屬性一定是 KVC 的,因?yàn)檫@里的狀態(tài)屬性都是布爾類型霹疫,按照蘋果的風(fēng)格拱绑,這些屬性的 getter 方法的前面都有 is 前綴,所以更米,這些屬性的 keypath 都是 is 開頭的欺栗。

回到如何實(shí)現(xiàn)異步的 NSOperation 上來(lái),官方的文檔說(shuō)了三點(diǎn):重寫 start 方法征峦,在合適的地方更新 executing 和 finished 的值并且產(chǎn)生 KVO 通知迟几,重寫 asynchronous 返回 true。

這里說(shuō)一下 start 方法和 main 方法栏笆,start 方法是 operation 開始的入口类腮,main 方法一般放具體需要執(zhí)行的代碼,在 NSOperation 的默認(rèn)實(shí)現(xiàn)里蛉加,start 方法會(huì)調(diào)用 main 方法蚜枢。

因?yàn)橹貙懥?start 方法,所以 KVO 通知必須由你自己來(lái)管理针饥,官方文檔里是通過(guò)兩個(gè) ivar 來(lái)實(shí)現(xiàn)狀態(tài)值的更新厂抽。

@interface MyOperation : NSOperation {
    BOOL executing;
    BOOL finished;
}

既然要實(shí)現(xiàn)異步的 NSOperation,那么就必然要把需要執(zhí)行的代碼放到其他線程去執(zhí)行丁眼,官方文檔里直接把 main 方法 detach 到一個(gè)新的線程去執(zhí)行筷凤。

值得注意的是,main 方法并沒(méi)有特別的作用,你完全可以寫一個(gè)其他的方法來(lái)寫你需要移步執(zhí)行的代碼藐守。比如下面提到的 AFN 2.0 里面就沒(méi)有用到 main 方法挪丢,而是自己寫了operationDidStart 來(lái)做工作。

[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];

除了官方文檔的這種實(shí)現(xiàn)方法外卢厂,業(yè)內(nèi)用的最多的是用一個(gè)枚舉來(lái)標(biāo)示狀態(tài)乾蓬,這里用 Swift 來(lái)演示:

public class ConcurrentOperation: NSOperation {
  public enum State: String {
    case Ready, Executing, Finished
    
    private var keyPath: String {
      return "is" + rawValue
    }
  }
  
  public var state = State.Ready {
    willSet {
      willChangeValueForKey(newValue.keyPath)
      willChangeValueForKey(state.keyPath)
    }
    didSet {
      didChangeValueForKey(oldValue.keyPath)
      didChangeValueForKey(state.keyPath)
    }
  }
}

可以看到這樣帶來(lái)的效果很顯著,在任何地方更新 state 屬性都會(huì)發(fā)出相應(yīng)的 KVO 通知慎恒。

OC 的版本可以去看 AFNetworking 2.x 的 AFURLConnectionOperation 的實(shí)現(xiàn)任内。另外,蘋果在 wwdc 2015 session 226 的這個(gè) demo 有關(guān)于異步 NSOperation 更完善復(fù)雜的封裝巧号。

講到這里族奢,相信你對(duì) NSOperation 里面的一些疑惑點(diǎn)有了一些基本的認(rèn)識(shí),接下來(lái)更深入的使用丹鸿,可以去看看官方文檔或其他文章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棚品,一起剝皮案震驚了整個(gè)濱河市靠欢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铜跑,老刑警劉巖门怪,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異锅纺,居然都是意外死亡掷空,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門囤锉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坦弟,“玉大人,你說(shuō)我怎么就攤上這事官地∧鸢” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵驱入,是天一觀的道長(zhǎng)赤炒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)亏较,這世上最難降的妖魔是什么莺褒? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮雪情,結(jié)果婚禮上遵岩,老公的妹妹穿的比我還像新娘。我一直安慰自己旺罢,他們只是感情好旷余,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布绢记。 她就那樣靜靜地躺著,像睡著了一般正卧。 火紅的嫁衣襯著肌膚如雪蠢熄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天炉旷,我揣著相機(jī)與錄音签孔,去河邊找鬼。 笑死窘行,一個(gè)胖子當(dāng)著我的面吹牛饥追,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罐盔,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼但绕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了惶看?” 一聲冷哼從身側(cè)響起捏顺,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纬黎,沒(méi)想到半個(gè)月后幅骄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡本今,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年拆座,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冠息。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挪凑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铐达,到底是詐尸還是另有隱情岖赋,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布瓮孙,位于F島的核電站唐断,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏杭抠。R本人自食惡果不足惜脸甘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望偏灿。 院中可真熱鬧丹诀,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至枚荣,卻和暖如春碗脊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背橄妆。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工衙伶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人害碾。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓矢劲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親慌随。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芬沉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容