構(gòu)造異步NSOperation進(jìn)行異步任務(wù)調(diào)度

開(kāi)發(fā)中經(jīng)常遇到異步任務(wù)之間有依賴關(guān)系坯认,需要對(duì)執(zhí)行順序進(jìn)行調(diào)度的情況。

比如氓涣,一個(gè)頁(yè)面要組合多個(gè)后端接口的數(shù)據(jù)牛哺,必須所有請(qǐng)求都完成后,再進(jìn)行數(shù)據(jù)組裝劳吠,最后刷新UI引润。

如果是同步任務(wù),解決方案很簡(jiǎn)單痒玩〈靖剑可以用dispatch_group,更方便的是用NSOperationaddDependency功能蠢古,讓最后執(zhí)行的任務(wù)依賴前面幾個(gè)任務(wù)即可奴曙。

我們知道,NSOperation內(nèi)部維護(hù)了一個(gè)狀態(tài)機(jī)來(lái)表示內(nèi)部任務(wù)的執(zhí)行狀態(tài)草讶。一共有下面幾個(gè)狀態(tài):

  • ready
  • executing
  • finished
  • cancelled

如果我們用addDependency給兩個(gè)NSOperation設(shè)置了依賴關(guān)系缆毁,那么一個(gè)NSOperation對(duì)應(yīng)的方法或block執(zhí)行完畢后,會(huì)變?yōu)閒inished狀態(tài)到涂,這時(shí)另一個(gè)NSOperation才會(huì)執(zhí)行脊框。

如果我們能讓異步任務(wù)表現(xiàn)得像同步任務(wù)一樣,在異步任務(wù)收到回調(diào)后才變?yōu)?code>finished狀態(tài)践啄,不就可以用addDependency來(lái)控制異步任務(wù)的執(zhí)行順序了嗎浇雹?

事實(shí)上蘋(píng)果在NSOperation的接口里給我們留了一個(gè)口子∮旆恚看NSOperation的接口:

NS_CLASS_AVAILABLE(10_5, 2_0)
@interface NSOperation : NSObject {
@private
    id _private;
    int32_t _private1;
#if __LP64__
    int32_t _private1b;
#endif
}

- (void)start;
- (void)main;

@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;

@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
@property (readonly, getter=isReady) BOOL ready;

- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

@property (readonly, copy) NSArray<NSOperation *> *dependencies;

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

@property NSOperationQueuePriority queuePriority;

@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);

- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);

@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);

@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);

@end

我們需要重點(diǎn)關(guān)注的是asynchronous屬性昭灵。如果它值為true,那么這個(gè)NSOperation執(zhí)行完畢后不會(huì)自動(dòng)變?yōu)閒inished狀態(tài)伐谈,需要手動(dòng)設(shè)置烂完。這正是我們想要的。

我們可以寫(xiě)一個(gè)NSOperation的子類诵棵,給異步任務(wù)提供一個(gè)設(shè)為finished狀態(tài)的接口抠蚣。

上代碼:

typealias MLAsyncOperationBlock = (operation:MLAsyncOperation)->Void

class MLAsyncOperation: NSOperation {
    private var ml_executing = false{
        willSet {
            willChangeValueForKey("isExecuting")
        }
        didSet {
            didChangeValueForKey("isExecuting")
        }
    }
    private var ml_finished = false{
        willSet {
            willChangeValueForKey("isFinished")
        }
        didSet {
            didChangeValueForKey("isFinished")
        }
    }
    
    private var block:MLAsyncOperationBlock?
    
    override var asynchronous:Bool {
        return true
    }
    
    override var concurrent:Bool {
        return true
    }
    
    override var finished:Bool{
        return ml_finished
    }
    
    override var executing:Bool{
        return ml_executing
    }
    
    convenience init(operationBlock:MLAsyncOperationBlock) {
        self.init()
        block = operationBlock
    }
    
    override func start() {
        if cancelled {
            ml_finished = true
            return
        }
        ml_executing = true
        block?(operation: self)
    }
    
    func finishOperation(){
        ml_executing = false
        ml_finished = true
    }
    
    deinit{
        print("operation deinited")
    }
}

幾個(gè)需要說(shuō)明的點(diǎn):

一:

NSOperation內(nèi)部有一組成員變量來(lái)維護(hù)它的executing、finished這些狀態(tài)履澳,我們?cè)L問(wèn)不到嘶窄。但我們可以另外加一組成員變量怀跛,自己來(lái)維護(hù)這些狀態(tài)。一個(gè)子類不一定要訪問(wèn)父類的成員變量,只要接口表現(xiàn)得和父類一樣就行了。

二:

NSOperationQueue是通過(guò)KVO觀察內(nèi)部的NSOperation狀態(tài)的變化巡社,來(lái)自動(dòng)管理NSOperation的執(zhí)行的。我們?cè)谠O(shè)置自己的ml_executing屬性的時(shí)候漓拾,需要表現(xiàn)得像executing屬性被設(shè)置了一樣。也就是需要調(diào)用一下willChangeValueForKey("isExecuting")didChangeValueForKey("isExecuting")兩個(gè)方法戒祠。利用Swift屬性的willSet和didSet特性晦攒,可以非常方便地實(shí)現(xiàn)。

finished屬性同理得哆。

三:

finishOperation這個(gè)方法,是用戶在收到異步回調(diào)哟旗,任務(wù)完成后需要調(diào)用的贩据。調(diào)用后這個(gè)operation就會(huì)變?yōu)閒inished狀態(tài)。為了方便闸餐,我給MLAsyncOperationBlock加了一個(gè)MLAsyncOperation類型的參數(shù)饱亮,在調(diào)用內(nèi)部block的時(shí)候會(huì)把self傳進(jìn)去。這樣用戶在構(gòu)造任務(wù)block的時(shí)候舍沙,通過(guò)這個(gè)參數(shù)就直接可以訪問(wèn)到operation本身了近上。

來(lái)簡(jiǎn)單測(cè)試一下好不好用。

先寫(xiě)一個(gè)類實(shí)現(xiàn)一個(gè)同步方法和兩個(gè)異步方法:

class TestClass {
    
    let queue = dispatch_queue_create("TestClass_background_queue", DISPATCH_QUEUE_CONCURRENT)

    func method1(){
        print("method 1 begin")
        for _ in 0 ... 100000 {
            continue
        }
        print("method 1 end")
    }
    
    func asyncMethod1(done:()->Void){
        print("async method 1 begin")
        dispatch_async(queue) { () -> Void in
            for _ in 0 ... 100000 {
                continue
            }
            print("async method 1 end")
            done()
        }
        return
    }
    
    func asyncMethod2(done:()->Void){
        print("async method 2 begin")
        dispatch_async(queue) { () -> Void in
            for _ in 0 ... 100000 {
                continue
            }
            print("async method 2 end")
            done()
        }
        return
    }
}

測(cè)試代碼:

let operationQueue = NSOperationQueue()
operationQueue.maxConcurrentOperationCount = 5

let object = TestClass()

let op1 = NSBlockOperation { 
    object.method1()
}

let asyncOp1 = MLAsyncOperation { (operation) in
    object.asyncMethod1{ () -> Void in
        operation.finishOperation()
    }
}

let asyncOp2 = MLAsyncOperation { (operation) in
    object.asyncMethod2{
        operation.finishOperation()
    }
}

op1.addDependency(asyncOp1)
op1.addDependency(asyncOp2)

operationQueue.addOperation(asyncOp1)
operationQueue.addOperation(asyncOp2)
operationQueue.addOperation(op1)

let runloop = NSRunLoop.currentRunLoop()
while runloop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture()){
    continue
}

由于測(cè)試工程是一個(gè)command line tool拂铡,我用runloop阻塞住了主線程壹无,避免主線程執(zhí)行完之后整個(gè)程序退出,operationQueue中的代碼來(lái)不及執(zhí)行感帅。

跑一下斗锭,輸出結(jié)果如下:

async method 1 begin
async method 2 begin
async method 1 end
async method 2 end
method 1 begin
method 1 end

可以看到,兩個(gè)異步方法并行執(zhí)行失球,在兩個(gè)方法都收到回調(diào)完成之后岖是,最后一個(gè)方法開(kāi)始執(zhí)行。完美實(shí)現(xiàn)了我們的需求实苞。

完整的代碼可以從我的Github下載豺撑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市黔牵,隨后出現(xiàn)的幾起案子聪轿,更是在濱河造成了極大的恐慌,老刑警劉巖猾浦,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屹电,死亡現(xiàn)場(chǎng)離奇詭異阶剑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)危号,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)牧愁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人外莲,你說(shuō)我怎么就攤上這事猪半。” “怎么了偷线?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵磨确,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我声邦,道長(zhǎng)乏奥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任亥曹,我火速辦了婚禮邓了,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘媳瞪。我一直安慰自己骗炉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布蛇受。 她就那樣靜靜地躺著句葵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兢仰。 梳的紋絲不亂的頭發(fā)上乍丈,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音把将,去河邊找鬼诗赌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛秸弛,可吹牛的內(nèi)容都是我干的铭若。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼递览,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叼屠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起绞铃,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤镜雨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后儿捧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體荚坞,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挑宠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颓影。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片各淀。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诡挂,靈堂內(nèi)的尸體忽然破棺而出碎浇,到底是詐尸還是另有隱情,我是刑警寧澤璃俗,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布奴璃,位于F島的核電站,受9級(jí)特大地震影響城豁,放射性物質(zhì)發(fā)生泄漏苟穆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一唱星、第九天 我趴在偏房一處隱蔽的房頂上張望雳旅。 院中可真熱鬧,春花似錦魏颓、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仑濒,卻和暖如春叹话,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墩瞳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工驼壶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喉酌。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓热凹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親泪电。 傳聞我的和親對(duì)象是個(gè)殘疾皇子般妙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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