多線程原理--NSOperation胁孙、NSOperationQueue

多線程原理--NSOperation秒旋、NSOperationQueue

NSOperation類是iOS2.0推出的竞滓,通過NSThread實現(xiàn)的,但是效率一般。從iOS4推出GCD時又重寫了NSOperation和NSOperationQueue疫向,NSOperation和NSOperationQueue分別對應GCD的任務和隊列(了解GCD直通車:http://www.reibang.com/p/acc6e7bd6f10)咳蔚,所以NSOPeration和NSOperationQueue是基于GCD更高一層的封裝,而且完全地面向?qū)ο笊ν铡5潜菺CD更簡單易用谈火、代碼可讀性也更高。NSOperation和NSOperationQueue對比GCD會帶來一點額外的系統(tǒng)開銷舌涨,但是可以在多個操作Operation中添加附屬糯耍。

優(yōu)點

  • 可添加完成的代碼塊,在操作完成后執(zhí)行囊嘉。
  • 添加操作之間的依賴關系温技,方便的控制執(zhí)行順序。
  • 設定操作執(zhí)行的優(yōu)先級哗伯。
  • 可以很方便的取消一個操作的執(zhí)行荒揣。
  • 使用 KVO 觀察對操作執(zhí)行狀態(tài)的更改:isExecuteing、isFinished焊刹、isCancelled系任。

NSOperation、NSOperationQueue

NSOperation是一個和任務相關的抽象類虐块,不具備封裝操作的能力俩滥,必須使用其子類:NSInvocationOperation或者NSBlockOperation,當然也可以自定義子類,實現(xiàn)內(nèi)部相應的?法贺奠,NSOperation實例在多線程上執(zhí)行是線程安全的不需要添加額外的鎖霜旧,不 必管理線程生命周期和同步等問題。NSInvocationOperation 和NSBlockOperation子類不同的是儡率,因為NSInvocationOperation沒有額外添加任務的方法挂据,所以使用NSInvocationOperation創(chuàng)建的對象只會有一個任務,其次使用NSBlockOperation來執(zhí)行任務切任務大于1的時候儿普,系統(tǒng)可能會開辟新線程來異步執(zhí)行任務崎逃。

NSOperationQueue有主隊列和自定義隊列(串行和并發(fā)),將NSOperation對象添加NSOperationQueue中眉孩,該NSOperationQueue對象從線程中拿取操作个绍、以及分配到對應線程的工作都是由系統(tǒng)處理的勒葱。一般操作對象添加到NSOperationQueue之后,如果不存在依賴或者整個隊列被暫停情況下通常短時間就開始運行巴柿。NSOperationQueue可以通過對象屬性suspended來決定是否讓隊列暫時停止對任務的調(diào)度凛虽,或者cancel、cancelAllOperations方法來取消操作對象广恢,不過使用這兩個方法后操作對象無法恢復凯旋,操作時只會停止調(diào)度隊列中操作對象(注意:正在執(zhí)行的操作依然會執(zhí)行,無法取消袁波。 )瓦阐, 且取消不可恢復。

首先創(chuàng)建一個NSOperation的子類(以NSInvocationOperation為例)篷牌,再創(chuàng)建隊列NSOperationQueue睡蟋,最后將操作加入隊列。這樣我們就完成了多線程的操作枷颊〈辽保可以直接執(zhí)行start方法,但不會開辟新線程去執(zhí)行操作夭苗,而是在當前線程同步執(zhí)行任務信卡。這里注意,如果將操作添加到隊列的同時再次執(zhí)行start题造,這時會拋出異常傍菇,因為線程在創(chuàng)建后,開始進入Runnable就緒的狀態(tài)界赔,如果此時再次執(zhí)行start會重復初始化操作丢习。

    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];

    // 將op加入到隊列中
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op];
    [[NSOperationQueue mainQueue] addOperation:op];

    // 或者直接start
    [op start];

因為基于GCD更高一層的封裝,NSOperation同樣也具備線程之間的通訊以及控制并發(fā)數(shù)淮悼,舉個簡單的例子:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 2;
    for (int i = 0; i<10; i++) {
        [queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                NSLog(@"%d-%@--%@",i,[NSOperationQueue currentQueue],[NSThread currentThread]);
            }];
        }];
    }

由于設置了最大并發(fā)數(shù)maxConcurrentOperationCount為2咐低,所以每2秒輸出四個任務。

0-<NSThread: 0x600003f08180>{number = 4, name = (null)}
1-<NSThread: 0x600003f17bc0>{number = 5, name = (null)}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
2-<NSThread: 0x600003f31e00>{number = 7, name = (null)}
3-<NSThread: 0x600003f08180>{number = 4, name = (null)}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
... // 省略部分相似打印信息
9-<NSThread: 0x600003f08180>{number = 4, name = (null)}
8-<NSThread: 0x600003f17bc0>{number = 5, name = (null)}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}

當然我們也可以自定義子類袜腥,可能會添加到串行和并發(fā)隊列的不同情況见擦,需要重寫不同的方法。TIP:并發(fā)操作時羹令,需要自己創(chuàng)建自動釋放池鲤屡,因為異步操作無法訪問主線程的自動釋放池。經(jīng)常通過cancelled屬性檢查方法是否取消福侈,并且對取消做出響應酒来。

操作依賴

NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之間的依賴關系癌刽,可以使用依賴關系來控制操作間的啟動順序役首。當一個操作對象添加了依賴显拜,被依賴的操作對象就會先執(zhí)行衡奥,當被依賴的操作對象執(zhí)行完才會當前的操作對象,通過操作依賴可以很方便的按照特定順序控制操作之間的執(zhí)行先后順序远荠。操作對象可以通過addDependency添加和removeDependency移除依賴矮固。在添加線程對象NSOperationQueue之前進行依賴設置,操作對象會管理自己的依賴譬淳,因此在不相同隊列中的操作對象可以建立依賴關系档址。

舉例:現(xiàn)在有任務1、2邻梆、3守伸,當任務1執(zhí)行完畢后再執(zhí)行任務2,任務2執(zhí)行完畢后再執(zhí)行任務3浦妄。

    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"任務----1");
    }];
    
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"任務----2");
    }];
    
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"任務----3"); 
    }];
    
    // 建立依賴
    [bo2 addDependency:bo1];
    [bo3 addDependency:bo2];

    [self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
    
    NSLog(@"執(zhí)行結(jié)束");

這里waitUntilFinished如果設置為YES尼摹,則會堵塞當前線程,直到該操作結(jié)束剂娄。
最終打印效果總是:任務1->任務2->任務3蠢涝。

 任務----1
 任務----2
 任務----3
 執(zhí)行結(jié)束

優(yōu)先級、服務質(zhì)量

NSOperation 提供了queuePriority(優(yōu)先級)屬性阅懦,queuePriority屬性適用于同一操作隊列中的操作和二,不適用于不同操作隊列中的操作。默認情況下耳胎,所有新創(chuàng)建的操作對象優(yōu)先級都是NSOperationQueuePriorityNormal惯吕。但是我們可以通過setQueuePriority方法來改變當前操作在同一隊列中的執(zhí)行優(yōu)先級。在iOS 8.0后场晶,推出了服務質(zhì)量混埠,通過設置服務質(zhì)量讓系統(tǒng)優(yōu)先處理某一個操作。

NSOperation優(yōu)先級的枚舉和Quality of Service枚舉:

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

// ----------------------------
// NSObjCRuntime.h
typedef NS_ENUM(NSInteger, NSQualityOfService) {
//與用戶交互的任務诗轻,這些任務通常跟UI級別的刷新相關钳宪,比如動畫,這些任務需要在一瞬間完成.
    NSQualityOfServiceUserInteractive = 0x21,
// 由用戶發(fā)起的并且需要立即得到結(jié)果的任務扳炬,比如滑動scroll view時去加載數(shù)據(jù)用于后續(xù)cell的顯示吏颖,這些任務通常跟后續(xù)的用戶交互相關,在幾秒或者更短的時間內(nèi)完成
    NSQualityOfServiceUserInitiated = 0x19,
// 一些可能需要花點時間的任務恨樟,這些任務不需要馬上返回結(jié)果半醉,比如下載的任務,這些任務可能花費幾秒或者幾分鐘的時間
    NSQualityOfServiceUtility = 0x11,
// 一些可能需要花點時間的任務劝术,這些任務不需要馬上返回結(jié)果
    NSQualityOfServiceBackground = 0x09,
// 一些可能需要花點時間的任務缩多,這些任務不需要馬上返回結(jié)果
    NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

Utility 及以下的優(yōu)先級會受到 iOS9 中低電量模式的控制呆奕,另外在沒有用戶操作時,90% 任務的優(yōu)先級都應該在 Utility 之下衬吆。

一般對于添加到隊列中的任務梁钾,當這個任務的所有依賴都已經(jīng)完成時,任務通常會進入準備就緒狀態(tài)來等待執(zhí)行逊抡,這時該任務的開始執(zhí)行順序由任務的優(yōu)先級決定姆泻。如果一個隊列中既包含高優(yōu)先級就緒狀態(tài)的任務,又包含低優(yōu)先級就緒狀態(tài)的任務冒嫡,高優(yōu)先級就緒狀態(tài)的任務會優(yōu)先被執(zhí)行拇勃;

服務質(zhì)量則根據(jù)CPU,網(wǎng)絡和磁盤的分配來創(chuàng)建一個操作的系統(tǒng)優(yōu)先級孝凌。一個高質(zhì)量的服務意味著可以提供更多的資源來更快的完成操作方咆,涉及到CPU調(diào)度的優(yōu)先級、IO優(yōu)先級蟀架、任務運行所在的線程以及運行的順序等等峻呛。正確的指定線程或任務優(yōu)先級可以讓系統(tǒng)更加智能的管理隊列的資源分配,以便于提高執(zhí)行效率和控制電量等辜窑。

自定義NSOperation子類

當NSInvocationOperation或者NSBlockOperation無法滿足我們?nèi)粘P枨蠊呈觯覀円部梢远x串行和并發(fā)的2種類型的NSOperation子類,注意需要創(chuàng)建自動釋放池穆碎,異步操作無法訪問主線程的自動釋放池牙勘。在自定義串行NSOperation子類時要重寫main方法并且最好添加一個init方法用于初始化數(shù)據(jù),在自定義并行NSOperation子類是需要重寫start所禀、isFinished方面、isAsynchronousisExecuting方法。經(jīng)常通過cancelled屬性檢查方法是否取消色徘,并且對取消的做出響應恭金,定期調(diào)用對象的isCancelled方法,如果返回“YES”褂策,則立即返回横腿,不再執(zhí)行任務。

如果想進一步了解自定義NSOperation子類的具體實現(xiàn)斤寂,接下來將利用自定義NSOperation子類耿焊,同時借鑒了AFNetworking、SDWebImage遍搞、YYKit的部分思想來實現(xiàn)具有緩存支持的異步圖像下載器罗侯。

該文章為記錄本人的學習路程,也希望能夠幫助大家溪猿,知識共享钩杰,共同成長纫塌,共同進步!=才护戳!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市垂睬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抗悍,老刑警劉巖驹饺,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缴渊,居然都是意外死亡赏壹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門衔沼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝌借,“玉大人,你說我怎么就攤上這事指蚁∑杏樱” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵凝化,是天一觀的道長稍坯。 經(jīng)常有香客問我,道長搓劫,這世上最難降的妖魔是什么瞧哟? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮枪向,結(jié)果婚禮上勤揩,老公的妹妹穿的比我還像新娘。我一直安慰自己秘蛔,他們只是感情好陨亡,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著深员,像睡著了一般数苫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辨液,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天虐急,我揣著相機與錄音,去河邊找鬼滔迈。 笑死止吁,一個胖子當著我的面吹牛被辑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敬惦,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盼理,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俄删?” 一聲冷哼從身側(cè)響起宏怔,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎畴椰,沒想到半個月后臊诊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡斜脂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年抓艳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帚戳。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡玷或,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出片任,到底是詐尸還是另有隱情偏友,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布对供,位于F島的核電站约谈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏犁钟。R本人自食惡果不足惜棱诱,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涝动。 院中可真熱鬧迈勋,春花似錦、人聲如沸醋粟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽米愿。三九已至厦凤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間育苟,已是汗流浹背较鼓。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人博烂。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓香椎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親禽篱。 傳聞我的和親對象是個殘疾皇子畜伐,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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