嘮嗑
最近開始遷移文章,把以前寫的一些文章都遷移到簡書,也算開始在簡書安家了。
前言
本文是對([WWDC 2015 Session 226: Advanced NSOperations]) 的一個小結(jié),在這個視頻中介紹了用NSOperation來組織App中的功能結(jié)構(gòu),使邏輯更清晰。如果沒有看過這個視頻可以看看視頻,有助于加深對operation的理解。這篇文章更多的是一些理論知識,沒什么代碼,但是理解一些內(nèi)部原理有助于平時我們?nèi)タ吹谌綆?,比?SDWebImage AFNetworking YYImage等里面對operation自定義的運用。
1.NSOperation 和NSOperationQueue簡介
NSOperationQueue是高級的dispatch_queue_t,但同時也提供了一些功能特性供開發(fā)者使用。
1.cancelAllOperations:可以取消隊列中所有還未執(zhí)行的operation
2.maxConcurrentOperationCount:最大并行并發(fā)數(shù)
我們來看一張圖

如果我們設(shè)置maxConcurrentOperationCount=2,我們可以將其看成一個并行隊列,同一時刻允許最大的并發(fā)數(shù)為2。只有上一個operation做完后,下一個operation才會開始。maxConcurrentOperationCount的默認值為default,意思是系統(tǒng)會開設(shè)適當數(shù)量的子線程。
NSOperation的特點
- 可以cancel未執(zhí)行的operation
- 指定相同或者不同隊列的依賴關(guān)系
- kvo監(jiān)控NSOperation的對象屬性
- 指定操作優(yōu)先級
- 重用operation:operation只能被執(zhí)行一次后生命周期就結(jié)束了,不能被多次執(zhí)行。
NSOperation可以看成dispatch_block_t的封裝。我們來看看operation 的生命周期(圖1),當我們init一個operation,它的初始狀態(tài)是Pending,然后在某一時刻會到Ready,這時operation從queue中拿出開始執(zhí),進入executing狀態(tài),最后執(zhí)行完畢到finished狀態(tài)。在除finished以外的其它三個狀態(tài)都可以到cancelled狀態(tài)。
圖1

operation各個狀態(tài)說明:
Ready:一個operation只有在Ready狀態(tài)才可以被queue執(zhí)行。下圖,在一個串行的queue中,即使第一次進入Ready狀態(tài)的operation是在第四個被放進去的,但是還是第一個執(zhí)行。這樣的好處就是,放入串行隊列的operation我們不需要在operation定義的時候就確定它的執(zhí)行順序,而是在后面動態(tài)的確定執(zhí)行順序。(控制進入Ready狀態(tài)的時機)
image

用這個Ready屬性我們可以做什么事兒呢?我們可以確定依賴(dependencies)。默認條件下,如果A依賴于B,當A進入finish狀態(tài),那么B才會進入Ready狀態(tài)。這個依賴可以存在于不同的operationQueue。一個operaion直有它依賴的所有operation處于finish狀態(tài),它才回進入ready狀態(tài)。如果我們用queue來管理operation,operation在加入到queue中的某一個時刻,它會在它依賴的operaions執(zhí)行完畢(或者我們沒有設(shè)置依賴),在某一個時刻自動進入Ready狀態(tài)并被執(zhí)行。當然我們可以手動執(zhí)行operation,但是官方不推薦,因為調(diào)用一個未Ready狀態(tài)的operation會導(dǎo)致異常拋出。
Cancel:可以cancel未執(zhí)行的operation,該方法會設(shè)置對象內(nèi)的標志位,表明operation不需要執(zhí)行,如果operation已經(jīng)start,就不能取消了。operation進入cancel狀態(tài)只是一個Bool值的翻轉(zhuǎn),當我們繼承operation,我們要自己決定cancel時候需要做什么業(yè)務(wù)。比如我們在執(zhí)行一個網(wǎng)絡(luò)請求,cancel狀態(tài)代表著cancel 一個urlSession。當我們請求一個數(shù)據(jù)庫,cancel代表我們停止對數(shù)據(jù)庫進行讀寫操作。所以我們最好KVO這個屬性,然后做適當?shù)倪壿嫛<尤氲絨ueue的operaion如果不處于finish狀態(tài),可以進入cancel狀態(tài)。調(diào)用[operaion cancel]。默認條件下,operation調(diào)用cancel,也會標志它進入finished狀態(tài)。因為只有這樣依賴它的operation才有機會被執(zhí)行。如果我們自定義operation,我們要考慮它進入cancel狀態(tài)或者這個operation沒有被成功執(zhí)行時候,根據(jù)業(yè)務(wù)是否應(yīng)該讓它進入finished。
KVO:operation 的狀態(tài)是滿足KVO的,可以被監(jiān)聽,可以被監(jiān)聽的keyPath(isCancelled,isAsynchronous,isExecuting,isFinished,isReady等)。注意:因為operaion可能在任何線程內(nèi)被執(zhí)行,如果我們將UI元素和以上屬性做綁定,那么KVO過來的行為就可能發(fā)生在任何的線程內(nèi)。保險的做法是:如果KVO得知通知后要做UI的事情,都扔到主線程中做。如果我們在自定義operaion時候重寫以上屬性,也要確保它們可以被KVO和KVC。
線程安全:系統(tǒng)提供的operaion的子類都是線程安全的,所有用系統(tǒng)的operation我們不需要加鎖,當我們自定義operaion,提供一些對外的數(shù)據(jù)存取的方法自己加鎖。
同步和異步的Operaions:
如果我們手動執(zhí)行一個operation,而不是把它放入一個operationQueue進行管理。operation自己本身也有同步和異步之分,(默認同步)。同步的operation不會開子線程,會在調(diào)用start這個方法的線程內(nèi)執(zhí)行。而異步的會自己開啟子線程,如果我們用queue來管理operaion,我們希望它是同步的,因為queue自己會開子線程,我們只要保證加入queue的operation可以按次序執(zhí)行就行。
如果我們不想用queue和GCD來管理operation但想要利用子線程執(zhí)行operation,我們應(yīng)該自定義一個異步的operation。我們需要追蹤operation的狀態(tài)而且手動觸發(fā)KVO?;蛘哂肒VC觸發(fā)KVO。其實我們平時在用點語法用系統(tǒng)的setter的時候,它是通過KVC設(shè)置的,所以自動會觸發(fā)KVO通知,如果我們重載了setter,為了讓屬性符合KVO條件,必須手動觸發(fā)通知。

作為抽象類,我們可以繼承NSOpration,實現(xiàn)自己的operation來代碼業(yè)務(wù)邏輯。
同步的operation條件:
1.重載main方法,這個方法里面放主業(yè)務(wù)邏輯。也需要查看cancel屬性
2.如果我們重載了getter和setter,必須確保調(diào)用是線程安全的。
異步operation條件:
1.重載start方法,和asynchronous,exciting,finished屬性
start方法中我們必須確保operation異步執(zhí)行,在此方法中我們需要改變executing更新狀態(tài),發(fā)送executing KVO通知。當結(jié)束operation。必須更新isExecuting和isFinished狀態(tài),并觸發(fā)kvo通知。當cancel一個operation時候,我們也要更新isFinished狀態(tài),即使此時operation還未執(zhí)行。在queue 中的operation必須進入cancel狀態(tài)后才可以被從operation中移除,
注意:start里面不能調(diào)用[super start];在自定義異步operation中,我們完全自定義start,已經(jīng)全部模擬了父類默認的start行為(start task && send KVO),在start方法里面,我們還要查看isCanceled屬性,確保start task前,task是不是已經(jīng)被取消。如果我們自定義了dependency,我們還需要發(fā)送isReady的KVO通知。