蘋果在并發(fā)編程方面毒费,除了提供有GCD外炸卑,還有NSOperation
與NSOperationQueue
組合减拭。
GCD是純C的API般此,而NSOperation
與NSOperationQueue
是基于 GCD 更高一層的封裝唠亚,是Objective-C的對象链方。
在GCD中,任務(wù)用塊來表示灶搜,而塊是個輕量級數(shù)據(jù)結(jié)構(gòu)(參見第37條)祟蚀。與之相反,NSOperation
與NSOperationQueue
則是個更為重量級的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)原則擦俐。
NSOperation
和NSOperationQueue
簡介
-
NSOperation
NSOperation
是系統(tǒng)提供的抽象的基類,我們使用的時候需要使用繼承于它的子類握侧。系統(tǒng)為我們提供了兩種繼承于NSOperation
的子類蚯瞧,分別是NSInvocationOperation
和NSBlockOperation
,大多情況下品擎,我們用這兩個系統(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【操作】通俗的講洪规,就是我們寫在程序里的一段代碼
。
NSOperation
與NSOperationQueue
使用步驟
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)屬性isExecuting
和isFinished
栈妆。當(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)最大值}籍救。
依賴
NSOperation
與NSOperationQueue
最吸引人的地方是它能添加操作之間的依賴關(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)系。
要點
在解決多線程與任務(wù)管理問題時啄踊,派發(fā)隊列并非唯一方案忧设。
操作隊列提供了一套高層的Objective-C API,能實現(xiàn)純GCD所具備的絕大部分功能社痛,而且還能完成一些更為復(fù)雜的操作见转,那些操作若改用GCD來實現(xiàn),則需另外編寫代碼蒜哀。