52個有效方法(43) - 掌握GCD及操作隊列的使用時機(jī)

蘋果在并發(fā)編程方面毒费,除了提供有GCD外炸卑,還有NSOperationNSOperationQueue組合减拭。

GCD是純C的API般此,而NSOperationNSOperationQueue是基于 GCD 更高一層的封裝唠亚,是Objective-C的對象链方。

在GCD中,任務(wù)用塊來表示灶搜,而塊是個輕量級數(shù)據(jù)結(jié)構(gòu)(參見第37條)祟蚀。與之相反,NSOperationNSOperationQueue則是個更為重量級的Objective-C對象,是封裝GCD實現(xiàn)Objective-C API割卖。

使用 NSOperation與NSOperationQueue的好處

  • 取消某個操作前酿。如果使用操作隊列,那么想要取消操作隊列是很容易的究珊。運行任務(wù)之前薪者,可以在NSOperation對象上調(diào)用cancel方法,該方法會設(shè)置對象內(nèi)的標(biāo)志位剿涮,用以表明此任務(wù)不需執(zhí)行言津,不過攻人,已經(jīng)啟動的任務(wù)無法取消。若是不使用操作隊列悬槽,而是把塊安排到GCD隊列怀吻,那就無法取消了。開發(fā)者可以在應(yīng)用程序?qū)幼约簛韺崿F(xiàn)取消功能初婆,不過這樣做需要編寫很多代碼蓬坡,而那些代碼其實已經(jīng)由操作隊列實現(xiàn)好了。

  • 指定操作間的依賴關(guān)系磅叛。一個操作可以依賴其他多個操作屑咳。開發(fā)者能夠制定操作之間的依賴體系,使特定的操作必須在另外一個操作順利執(zhí)行完畢后方可執(zhí)行弊琴。
    通過鍵值觀測機(jī)制監(jiān)控NSOperation對象的屬性兆龙。NSOperation對象有許多屬性都適合通過鍵值觀測機(jī)制(簡稱KVO)來監(jiān)聽。比如可以通過isCancelled屬性來判斷任務(wù)是否已取消敲董,又比如可以通過isFinished屬性來判斷任務(wù)是否已完成紫皇。

  • 指定操作的優(yōu)先級。操作的優(yōu)先級表示此操作與隊列中其他操作之間的優(yōu)先級關(guān)系腋寨。優(yōu)先級高的操作先執(zhí)行聪铺,優(yōu)先級低的后執(zhí)行。操作隊列的調(diào)度算法(scheduling algorithm)雖“不透明”(opaque)萄窜,但必然是經(jīng)過一番深思熟慮才寫成的铃剔。反之,GCD則沒有直接實現(xiàn)此功能的辦法脂倦。GCD的隊列確實有優(yōu)先級番宁,不過那是針對整個隊列來說的,而不是針對每個塊來說的赖阻。NSOperation對象也有“線程優(yōu)先級”(thread priority)蝶押,這決定了運行此操作的線程處在何種優(yōu)先級上。用GCD也可實現(xiàn)此功能火欧,然而采用操作隊列更簡單棋电,只需設(shè)置一個屬性。

  • 重用NSOperation對象苇侵。系統(tǒng)內(nèi)置了一些NSOperation的子類(比如NSBlockOperation)以供開發(fā)者調(diào)用赶盔,要是不想用這些固有子類的話,那就得自己來創(chuàng)建了榆浓。這些類就是普通的Objective-C對象于未,能夠存放任何信息。對象在執(zhí)行時可以充分利用存于其中的信息,而且還可以隨意調(diào)用定義在類中的方法烘浦。這就比派發(fā)隊列中那些簡單的塊要強(qiáng)大許多抖坪。這些NSOperation類可以在代碼中多次使用,他們符合軟件開發(fā)中的“不重復(fù)”(Do’t Repeat Yourself闷叉,DRY)原則擦俐。

NSOperationNSOperationQueue簡介

  • NSOperation

NSOperation是系統(tǒng)提供的抽象的基類,我們使用的時候需要使用繼承于它的子類握侧。系統(tǒng)為我們提供了兩種繼承于NSOperation的子類蚯瞧,分別是NSInvocationOperationNSBlockOperation,大多情況下品擎,我們用這兩個系統(tǒng)提供的子類就能滿足我們并發(fā)編程的任務(wù)埋合,但是如果你不想用系統(tǒng)提供的這兩個類,那么你可以根據(jù)自己的需求來自定義自己的操作類萄传。

  • NSOperationQueue
  • NSOperationQueue:用來存放操作的隊列饥悴。是由GCD提供的一個隊列模型的Cocoa抽象。GCD提供了更加底層的控制盲再,而操作隊列則在GCD之上實現(xiàn)了一些方便的功能。

  • NSOperationQueue操作隊列中的任務(wù)的執(zhí)行順序收到任務(wù)的isReady【就緒狀態(tài)】狀態(tài)和任務(wù)的隊列優(yōu)先級影響瓣铣。這和GCD中的隊列FIFO的執(zhí)行順序有很大區(qū)別答朋。

  • 我們可以通過設(shè)置NSOperationQueue的最大并發(fā)操作數(shù)(maxConcurrentOperationCount)來控制任務(wù)執(zhí)行是并發(fā)還是串行。

  • NSOperationQueue有兩種不通類型的隊列:主隊列和自定義隊列棠笑。主隊列在主線程上運行梦碗,而自定義隊列在后臺執(zhí)行。這兩種隊列中加入的任務(wù)都需要用NSOperation的子類來表示蓖救。
    注:NSOperation【操作】通俗的講洪规,就是我們寫在程序里的一段代碼

NSOperationNSOperationQueue 使用步驟

NSOperation 需要配合 NSOperationQueue 來實現(xiàn)多線程循捺。因為默認(rèn)情況下斩例,NSOperation 單獨使用時,操作任務(wù)會在創(chuàng)建操作的線程中執(zhí)行从橘,配合 NSOperationQueue 我們能更好的實現(xiàn)異步執(zhí)行念赶。

NSOperation

NSOperation 是個抽象類,不能用來封裝操作恰力。我們只有使用它的子類來封裝操作叉谜。我們有三種方式來封裝操作。

  • 使用子類 NSInvocationOperation
  • 使用子類 NSBlockOperation
    • NSBlockOperation 提供了一個方法 addExecutionBlock:踩萎,通過 addExecutionBlock:就可以為 NSBlockOperation 添加額外的操作停局。這些操作(包括blockOperationWithBlock 中的操作)可以在不同的線程中同時(并發(fā))執(zhí)行。只有當(dāng)所有相關(guān)的操作已經(jīng)完成執(zhí)行時,才視為完成董栽。

    • 一般情況下码倦,如果一個 NSBlockOperation 對象封裝了多個操作。NSBlockOperation 是否開啟新線程裆泳,取決于操作的個數(shù)叹洲。如果添加的操作的個數(shù)多,就會自動開啟新線程工禾。當(dāng)然開啟的線程數(shù)是由系統(tǒng)來決定的运提。

  • 自定義繼承自 NSOperation 的子類,通過實現(xiàn)內(nèi)部相應(yīng)的方法來封裝操作闻葵∶癖茫可以通過重寫 main或者 start方法 來定義自己的 NSOperation 對象。重寫main方法比較簡單槽畔,我們不需要管理操作的狀態(tài)屬性isExecutingisFinished栈妆。當(dāng) main執(zhí)行完返回的時候,這個操作就結(jié)束了厢钧。
NSOperationQueue
  • 主隊列:凡是添加到主隊列中的操作鳞尔,都會放到主線程中執(zhí)行。
// 主隊列獲取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 自定義隊列(非主隊列):添加到這種隊列中的操作早直,就會自動放到子線程中執(zhí)行寥假。同時包含了:串行、并發(fā)功能霞扬。
// 自定義隊列創(chuàng)建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation加入到NSOperationQueue
  • - (void)addOperation:(NSOperation *)op;使用 NSOperation 子類創(chuàng)建操作糕韧,并使用 addOperation: 將操作加入到操作隊列后能夠開啟新線程,進(jìn)行并發(fā)執(zhí)行喻圃。
/**
 * 使用 addOperation: 將操作加入到操作隊列中
 */
- (void)addOperationToQueue {

    // 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.創(chuàng)建操作
    // 使用 NSInvocationOperation 創(chuàng)建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 使用 NSInvocationOperation 創(chuàng)建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    // 使用 NSBlockOperation 創(chuàng)建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];

    // 3.使用 addOperation: 添加所有操作到隊列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}
  • - (void)addOperationWithBlock:(void (^)(void))block;,將操作加入到操作隊列后能夠開啟新線程萤彩,進(jìn)行并發(fā)執(zhí)行。
/**
 * 使用 addOperationWithBlock: 將操作加入到操作隊列中
 */

- (void)addOperationWithBlockToQueue {
    // 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.使用 addOperationWithBlock: 添加操作到隊列中
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
}
NSOperationQueue 控制串行執(zhí)行斧拍、并發(fā)執(zhí)行
  • 最大并發(fā)操作數(shù)maxConcurrentOperationCount控制的不是并發(fā)線程的數(shù)量雀扶,而是一個隊列中同時能并發(fā)執(zhí)行的最大操作數(shù)。而且一個操作也并非只能在一個線程中運行肆汹。

  • maxConcurrentOperationCount 默認(rèn)情況下為-1怕吴,表示不進(jìn)行限制,可進(jìn)行并發(fā)執(zhí)行县踢。

  • maxConcurrentOperationCount 為1時转绷,隊列為串行隊列。只能串行執(zhí)行硼啤。

  • maxConcurrentOperationCount 大于1時议经,隊列為并發(fā)隊列。操作并發(fā)執(zhí)行,當(dāng)然這個值不應(yīng)超過系統(tǒng)限制煞肾,即使自己設(shè)置一個很大的值咧织,系統(tǒng)也會自動調(diào)整為 min{自己設(shè)定的值,系統(tǒng)設(shè)定的默認(rèn)最大值}籍救。

依賴

NSOperationNSOperationQueue 最吸引人的地方是它能添加操作之間的依賴關(guān)系习绢。通過操作依賴,我們可以很方便的控制操作之間的執(zhí)行先后順序蝙昙。NSOperation 提供了3個接口供我們管理和查看依賴闪萄。

  • - (void)addDependency:(NSOperation *)op; 添加依賴,使當(dāng)前操作依賴于操作 op 的完成奇颠。

  • - (void)removeDependency:(NSOperation *)op;移除依賴败去,取消當(dāng)前操作對操作 op 的依賴。

  • @property (readonly, copy) NSArray<NSOperation *> *dependencies;在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對象數(shù)組烈拒。

/**
 * 操作依賴
 * 使用方法:addDependency:
 */
- (void)addDependency {

    // 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.創(chuàng)建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];

    // 3.添加依賴
    [op2 addDependency:op1]; // 讓op2 依賴于 op1圆裕,則先執(zhí)行op1,在執(zhí)行op2

    // 4.添加操作到隊列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

  • 依賴關(guān)系是單向的荆几,op1依賴于op2吓妆,op2的執(zhí)行與op1沒有任何關(guān)系。不能設(shè)置雙向依賴吨铸,如果op1依賴op2,op2又反過來依賴op1耿战,則會出現(xiàn)互相等待的死鎖情況。
優(yōu)先級

NSOperation 提供了queuePriority(優(yōu)先級)屬性焊傅,queuePriority屬性適用于同一操作隊列中的操作,不適用于不同操作隊列中的操作狈涮。默認(rèn)情況下狐胎,所有新創(chuàng)建的操作對象優(yōu)先級都是NSOperationQueuePriorityNormal。但是我們可以通過setQueuePriority:方法來改變當(dāng)前操作在同一隊列中的執(zhí)行優(yōu)先級歌馍。

// 優(yōu)先級的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
  • 操作有個isReady屬性握巢,該屬性表示操作時否處于就緒狀態(tài),處于就緒狀態(tài)的操作松却,只要等待系統(tǒng)調(diào)度暴浦,就會執(zhí)行。

  • 而操作的就緒狀態(tài)取決于依賴關(guān)系晓锻,當(dāng)op1依賴于op2的時候歌焦,如果op2還沒執(zhí)行完,op1的isReady = NO砚哆,即op1還處于未就緒狀態(tài)独撇。同處于就緒狀態(tài)的操作,此時再比較它們的隊列優(yōu)先級(queuePriority),這樣才有意義纷铣。

  • 隊列中會先執(zhí)行處于就緒狀態(tài)的操作卵史,即便處于就緒狀態(tài)的操作的隊列優(yōu)先級低于未就緒的操作。所以搜立,要控制操作之間的執(zhí)行順序以躯,需要使用依賴關(guān)系。

要點
  1. 在解決多線程與任務(wù)管理問題時啄踊,派發(fā)隊列并非唯一方案忧设。

  2. 操作隊列提供了一套高層的Objective-C API,能實現(xiàn)純GCD所具備的絕大部分功能社痛,而且還能完成一些更為復(fù)雜的操作见转,那些操作若改用GCD來實現(xiàn),則需另外編寫代碼蒜哀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末斩箫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子撵儿,更是在濱河造成了極大的恐慌乘客,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淀歇,死亡現(xiàn)場離奇詭異易核,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浪默,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門牡直,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纳决,你說我怎么就攤上這事碰逸。” “怎么了阔加?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵饵史,是天一觀的道長。 經(jīng)常有香客問我胜榔,道長胳喷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任夭织,我火速辦了婚禮吭露,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尊惰。我一直安慰自己奴饮,他們只是感情好纬向,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著戴卜,像睡著了一般逾条。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上投剥,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天师脂,我揣著相機(jī)與錄音,去河邊找鬼江锨。 笑死吃警,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的啄育。 我是一名探鬼主播酌心,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挑豌!你這毒婦竟也來了安券?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氓英,失蹤者是張志新(化名)和其女友劉穎侯勉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铝阐,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡址貌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了徘键。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片练对。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吹害,靈堂內(nèi)的尸體忽然破棺而出螟凭,到底是詐尸還是另有隱情,我是刑警寧澤赠制,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站挟憔,受9級特大地震影響钟些,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绊谭,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一政恍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧达传,春花似錦篙耗、人聲如沸迫筑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脯燃。三九已至,卻和暖如春蒙保,著一層夾襖步出監(jiān)牢的瞬間辕棚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工邓厕, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留逝嚎,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓详恼,卻偏偏與公主長得像补君,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子昧互,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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