多線程開發是日常開發任務中不可缺少的一部分,在iOS開發中常用到的多線程開發技術有
GCD、NSOperation、NSThread
,本文主要講解多線系列文章中關于NSOperation
的相關知識和使用詳解。
1、NSOperation簡介
NSOperation
是蘋果公司提供的一套完整的多線程解決方案,實際上它是基于GCD
更高一層的封裝,完全面向對象。相對于GCD而言使用更加的簡單、代碼更具可讀性。包括網絡請求、圖片壓縮在內的諸多多線程任務案例都很好的使用了NSOperation。當然NSOperation還需要NSOperationQueue
這一重要角色配合使用。
- 支持在操作對象之間依賴關系,方便控制執行順序。
- 支持可選的完成塊,它在操作的主要任務完成后執行。
- 支持使用KVO通知監視操作執行狀態的變化。
- 支持設定操作的優先級,從而影響它們的相對執行順序。
- 支持取消操作,允許您在操作執行時暫停操作。
2、NSOperation任務和隊列
2.1、NSOperation任務
和GCD一樣NSOperation同樣有任務的概念。所謂任務就是在線程中執行的那段代碼,在GCD中是放在block執行的,而在NSOperation中是在其子類 NSInvocationOperation
、NSBlockOperation
、自定義子類
中執行的。和GCD不同的是NSOperation需要NSOperationQueue的配合來實現多線程,NSOperation 單獨使用時是同步執行操作,配合 NSOperationQueue 才能實現異步執行。
2.2、NSOperation隊列
NSOperation中的隊列是用NSOperationQueue
表示的,用過來存放任務的隊列。
- 不同于GCD中隊列先進先出的原則,對于添加到NSOperationQueue隊列中的任務,首先根據任務之間的依賴關系決定任務的就緒狀態,然后進入就緒狀態的任務由任務之間的相對優先級決定開始執行順序。
- 同時NSOperationQueue提供設置最大并發任務數的途徑。
- NSOperationQueue還提供了兩種不同類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在后臺執行。
3、NSOperation的基本使用
NSOperation是一個抽象類,為了做任何有用的工作,它必須被子類化。盡管這個類是抽象的,但它給了它的子類一個十分有用而且線程安全的方式來建立狀態、優先級、依賴性和取消等的模型。NSOperation提供了三種方式來創建任務。
1、使用子類 NSInvocationOperation;
2、使用子類 NSBlockOperation;
3、自定義繼承自 NSOperation 的子類,通過實現內部相應的方法來封裝操作。
下面我們先來看下NSOperation上面三種不同方式的單獨使用情況。
3.1、NSInvocationOperation
NSInvocationOperation
類是NSOperation的一個具體子類,當運行時,它調用指定對象上指定的方法。使用此類可避免為應用程序中的每個任務定義大量自定義操作對象。
-(void)invocationOperation{
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation) object:nil];
[operation start];
}
-(void)operation{
for (int i = 0; i < 5; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}
打印結果:
2020-03-19 17:09:46.189458+0800 ThreadDemo[44995:12677738] 0--<NSThread: 0x600000ba9e40>{number = 1, name = main}
2020-03-19 17:09:48.190629+0800 ThreadDemo[44995:12677738] 1--<NSThread: 0x600000ba9e40>{number = 1, name = main}
2020-03-19 17:09:50.191219+0800 ThreadDemo[44995:12677738] 2--<NSThread: 0x600000ba9e40>{number = 1, name = main}
2020-03-19 17:09:52.192556+0800 ThreadDemo[44995:12677738] 3--<NSThread: 0x600000ba9e40>{number = 1, name = main}
2020-03-19 17:09:54.193900+0800 ThreadDemo[44995:12677738] 4--<NSThread: 0x600000ba9e40>{number = 1, name = main}
正如上面代碼運行的結果顯示,NSInvocationOperation單獨使用時,并沒有開啟新的線程,任務都是在當前線程中執行的。
3.2、NSBlockOperation
NSBlockOperation
類是NSOperation的一個具體子類,它充當一個或多個塊對象的包裝。該類為已經使用操作隊列且不希望創建分派隊列的應用程序提供了面向對象的包裝器。您還可以使用塊操作來利用操作依賴、KVO通知和其他可能與調度隊列不可用的特性。
-(void)blockOperationDemo{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}];
[operation start];
}
打印結果:
2020-03-19 17:19:38.673513+0800 ThreadDemo[45160:12689966] 0--<NSThread: 0x600001081100>{number = 1, name = main}
2020-03-19 17:19:40.675074+0800 ThreadDemo[45160:12689966] 1--<NSThread: 0x600001081100>{number = 1, name = main}
2020-03-19 17:19:42.676649+0800 ThreadDemo[45160:12689966] 2--<NSThread: 0x600001081100>{number = 1, name = main}
2020-03-19 17:19:44.677073+0800 ThreadDemo[45160:12689966] 3--<NSThread: 0x600001081100>{number = 1, name = main}
2020-03-19 17:19:46.677379+0800 ThreadDemo[45160:12689966] 4--<NSThread: 0x600001081100>{number = 1, name = main}
如上面代碼運行結果所示,NSBlockOperation單獨使用時,并未開啟新的線程,任務的執行都是在當前線程中執行的。
在NSBlockOperation類中還提供一個 addExecutionBlock
方法,這個方法可以添加一個代碼執行塊,當需要執行NSBlockOperation對象時,該對象將其所有塊提交給默認優先級的并發調度隊列。然后對象等待,直到所有的塊完成執行。當最后一個塊完成執行時,操作對象將自己標記為已完成。因此,我們可以使用塊操作來跟蹤一組執行的塊,這與使用線程連接來合并來自多個線程的結果非常相似。不同之處在于,由于塊操作本身在單獨的線程上運行,所以應用程序的其他線程可以在等待塊操作完成的同時繼續工作。需要說明的一點是,如果添加的任務較多的話,這些操作(包括 blockOperationWithBlock 中的操作)可能在不同的線程中并發執行,這是由系統決定的。
- (void)blockOperationDemo {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"blockOperation--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock1--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock2--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock3--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock4--%@", [NSThread currentThread]);
}
}];
[operation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"executionBlock5--%@", [NSThread currentThread]);
}
}];
[operation start];
}
打印結果:
2020-03-19 17:40:08.102543+0800 ThreadDemo[45536:12708941] executionBlock4--<NSThread: 0x600002a1ab00>{number = 1, name = main}
2020-03-19 17:40:08.102555+0800 ThreadDemo[45536:12709185] executionBlock2--<NSThread: 0x600002a57b80>{number = 8, name = (null)}
2020-03-19 17:40:08.102555+0800 ThreadDemo[45536:12709191] executionBlock5--<NSThread: 0x600002ab8980>{number = 9, name = (null)}
2020-03-19 17:40:08.102566+0800 ThreadDemo[45536:12709186] executionBlock3--<NSThread: 0x600002a7d440>{number = 4, name = (null)}
2020-03-19 17:40:08.102570+0800 ThreadDemo[45536:12709184] executionBlock1--<NSThread: 0x600002a3aa80>{number = 6, name = (null)}
2020-03-19 17:40:08.102576+0800 ThreadDemo[45536:12709187] blockOperation--<NSThread: 0x600002a7d600>{number = 5, name = (null)}
2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12709187] blockOperation--<NSThread: 0x600002a7d600>{number = 5, name = (null)}
2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12708941] executionBlock4--<NSThread: 0x600002a1ab00>{number = 1, name = main}
2020-03-19 17:40:10.103970+0800 ThreadDemo[45536:12709185] executionBlock2--<NSThread: 0x600002a57b80>{number = 8, name = (null)}
2020-03-19 17:40:10.103980+0800 ThreadDemo[45536:12709191] executionBlock5--<NSThread: 0x600002ab8980>{number = 9, name = (null)}
2020-03-19 17:40:10.103971+0800 ThreadDemo[45536:12709186] executionBlock3--<NSThread: 0x600002a7d440>{number = 4, name = (null)}
2020-03-19 17:40:10.103973+0800 ThreadDemo[45536:12709184] executionBlock1--<NSThread: 0x600002a3aa80>{number = 6, name = (null)}
正如上面代碼運行結果所示,在調用了addExecutionBlock方法添加了組個多的任務后,開啟新的線程,任務是并發執行的,blockOperationWithBlock中的任務執行也不是在當前的線程執行的。
3.3、自定義 NSOperation 的子類
如果使用子類 NSInvocationOperation、NSBlockOperation
不能滿足日常需求,我們還可以自定義子類。定一個類繼承自NSOperation
,重寫它的main
或者start
方法便可。
@interface CustomerOperation : NSOperation
@end
@implementation CustomerOperation
- (void)main{
if(!self.isCancelled){
for (int i = 0; i < 4; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"%d--%@",i,[NSThread currentThread]);
}
}
}
-(void)customerOperation{
CustomerOperation *operation = [[CustomerOperation alloc]init];
[operation start];
}
打印結果:
2020-03-19 20:28:54.473676+0800 ThreadDemo[47267:12811915] 0--<NSThread: 0x600001289040>{number = 1, name = main}
2020-03-19 20:28:56.474363+0800 ThreadDemo[47267:12811915] 1--<NSThread: 0x600001289040>{number = 1, name = main}
2020-03-19 20:28:58.474708+0800 ThreadDemo[47267:12811915] 2--<NSThread: 0x600001289040>{number = 1, name = main}
2020-03-19 20:29:00.476058+0800 ThreadDemo[47267:12811915] 3--<NSThread: 0x600001289040>{number = 1, name = main}
從上面代碼運行結果顯示可以看出,自定義的Operation并沒有開啟新的線程,任務的執行是在當前的線程中執行的。
上面講解了NSOperation單獨使用的情況,下面我們來看看NSOperationQueue隊列配合NSOperation的使用情況。
3.4、添加任務到隊列
在上面就已經提及過,NSOperation需要NSOperationQueue來配合使用實現多線程。那么我們就需要將創建好的NSOperation對象加載到NSOperationQueue隊列中。
NSOperationQueue提供了主隊列和自定義隊里兩種隊列,其中自定義隊列中包含了串行和并發兩種不同的功能。
- 主隊列:通過
[NSOperationQueue mainQueue]
方式獲取,凡是添加到主隊列中的任務都會放到主線程中執行。 - 自定義隊列:通過
[[NSOperationQueue alloc] init]
方式創建一個隊列,凡是添加到自定義隊列中的任務會自動放到子線程中執行。
3.4.1、addOperation
調用addOperation
方法將創建的operation對象添加到創建好的隊列中。
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
[queue addOperation:operation1];
[queue addOperation:operation2];
}
打印結果:
2020-03-19 21:01:45.868610+0800 ThreadDemo[47889:12843365] operation1--<NSThread: 0x6000012cd900>{number = 3, name = (null)}
2020-03-19 21:01:45.868610+0800 ThreadDemo[47889:12843364] operation2--<NSThread: 0x6000012e0640>{number = 6, name = (null)}
2020-03-19 21:01:47.872040+0800 ThreadDemo[47889:12843365] operation1--<NSThread: 0x6000012cd900>{number = 3, name = (null)}
2020-03-19 21:01:47.872040+0800 ThreadDemo[47889:12843364] operation2--<NSThread: 0x6000012e0640>{number = 6, name = (null)}
從上面代碼運行的結果可以看出,開啟了新的線程,任務是并發執行的。如果將queue換成是mainQueue,那么任務將會在主線程中同步執行。
3.4.2、addOperations
如果任務很多時,一個個添加到隊列不免有些麻煩,那么addOperations
就起作用了。
- (void)operationQueue {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
NSArray *operationList = @[operation1,operation2];
[queue addOperations:operationList waitUntilFinished:NO];
NSLog(@"end");
}
打印結果:
2020-03-19 21:06:30.381594+0800 ThreadDemo[48047:12849411] end
2020-03-19 21:06:32.385653+0800 ThreadDemo[48047:12849496] operation1--<NSThread: 0x600001f4e880>{number = 8, name = (null)}
2020-03-19 21:06:32.385651+0800 ThreadDemo[48047:12849498] operation2--<NSThread: 0x600001fac740>{number = 4, name = (null)}
2020-03-19 21:06:34.390373+0800 ThreadDemo[48047:12849496] operation1--<NSThread: 0x600001f4e880>{number = 8, name = (null)}
2020-03-19 21:06:34.390373+0800 ThreadDemo[48047:12849498] operation2--<NSThread: 0x600001fac740>{number = 4, name = (null)}
從上面代碼運行的記過可以看出,開啟了新的線程,任務是并發執行的。如果將queue換成是mainQueue,那么任務將會在主線程中同步執行。
這里需要說明的一點的是
waitUntilFinished
參數,如果傳YES,則表示會等待隊列里面的任務執行完成后才會往下執行,也就是會阻塞線程。
3.4.3、addOperationWithBlock
NSOperationQueue還提供了一個addOperationWithBlock
方法可以將operation對象添加到NSOperationQueue中。
-(void)addOperationWithBlock{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
}
打印結果:
2020-03-19 21:11:54.069593+0800 ThreadDemo[48192:12856146] operation1--<NSThread: 0x600000b0f740>{number = 4, name = (null)}
2020-03-19 21:11:54.069593+0800 ThreadDemo[48192:12856148] operation2--<NSThread: 0x600000b324c0>{number = 3, name = (null)}
2020-03-19 21:11:56.070432+0800 ThreadDemo[48192:12856148] operation2--<NSThread: 0x600000b324c0>{number = 3, name = (null)}
2020-03-19 21:11:56.070430+0800 ThreadDemo[48192:12856146] operation1--<NSThread: 0x600000b0f740>{number = 4, name = (null)}
從上面代碼運行的記過可以看出,開啟了新的線程,任務是并發執行的。如果將queue換成是mainQueue,那么任務將會在主線程中同步執行。
3.5、同步執行&并發執行
在前面的內容已經提及過,NSOperation單獨使用時默認是系統同步執行的,如果需要并發執行任務,就需要NSOperationQueue的協同。那么決定是并發執行還是同步執行的關鍵就在于最大并發任務數maxConcurrentOperationCount
。
- 默認情況下
maxConcurrentOperationCount
的值是-1,并不做限制,可以并發執行,如上面提到的NSBlockOperation添加多個任務塊。 -
maxConcurrentOperationCount
的值為1時,同步執行。 -
maxConcurrentOperationCount
的值大于1時,并發執行。 -
maxConcurrentOperationCount
的值并不是表示并發執行的線程數量,而是在一個隊列中能夠同時執行的任務的數量。
- (void)maxConcurrentOperationCount {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 1;//串行隊列
// queue.maxConcurrentOperationCount = 4;//并發隊列
NSLog(@"maxCount=%ld", (long)queue.maxConcurrentOperationCount);
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation1--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation2--%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"operation3--%@", [NSThread currentThread]);
}
}];
NSArray *operationList = @[operation1, operation2, operation3];
[queue addOperations:operationList waitUntilFinished:YES];
NSLog(@"end");
}
打印結果:
2020-03-19 21:35:41.878534+0800 ThreadDemo[48619:12879620] maxCount=1
2020-03-19 21:35:43.882396+0800 ThreadDemo[48619:12879824] operation1--<NSThread: 0x600000c7b240>{number = 3, name = (null)}
2020-03-19 21:35:45.882889+0800 ThreadDemo[48619:12879824] operation1--<NSThread: 0x600000c7b240>{number = 3, name = (null)}
2020-03-19 21:35:47.886984+0800 ThreadDemo[48619:12879824] operation2--<NSThread: 0x600000c7b240>{number = 3, name = (null)}
2020-03-19 21:35:49.888093+0800 ThreadDemo[48619:12879824] operation2--<NSThread: 0x600000c7b240>{number = 3, name = (null)}
2020-03-19 21:35:51.893354+0800 ThreadDemo[48619:12879824] operation3--<NSThread: 0x600000c7b240>{number = 3, name = (null)}
2020-03-19 21:35:53.894355+0800 ThreadDemo[48619:12879824] operation3--<NSThread: 0x600000c7b240>{number = 3, name = (null)}
2020-03-19 21:35:53.894723+0800 ThreadDemo[48619:12879620] end
從上面的代碼運行結果可以看出,開啟了新的線程,任務是串行執行的。
如果將maxConcurrentOperationCount的值修改為2,那么打印的記過如下:
2020-03-19 21:36:59.126533+0800 ThreadDemo[48668:12881702] maxCount=2
2020-03-19 21:37:01.130238+0800 ThreadDemo[48668:12881793] operation1--<NSThread: 0x600003a92280>{number = 5, name = (null)}
2020-03-19 21:37:01.130246+0800 ThreadDemo[48668:12881794] operation2--<NSThread: 0x600003a45840>{number = 6, name = (null)}
2020-03-19 21:37:03.133480+0800 ThreadDemo[48668:12881793] operation1--<NSThread: 0x600003a92280>{number = 5, name = (null)}
2020-03-19 21:37:03.133489+0800 ThreadDemo[48668:12881794] operation2--<NSThread: 0x600003a45840>{number = 6, name = (null)}
2020-03-19 21:37:05.137502+0800 ThreadDemo[48668:12881794] operation3--<NSThread: 0x600003a45840>{number = 6, name = (null)}
2020-03-19 21:37:07.140419+0800 ThreadDemo[48668:12881794] operation3--<NSThread: 0x600003a45840>{number = 6, name = (null)}
2020-03-19 21:37:07.140713+0800 ThreadDemo[48668:12881702] end
從上面的運行結果可以看出,開啟了新的線程,任務是并發執行的,而且每次執行的任務數最大為2個,那是因為我們設置了maxConcurrentOperationCount的值為2,而添加了3個任務在隊列中。
3.6、NSOperation線程間的通訊
多線程操作可能永遠也繞不過線程間通訊這個話題。通常我們將耗時的操作諸如網絡請求、文件上傳下載都放在子線程中執行,待執行完成之后需要回到主線程進行UI刷新操作,那么就會存在主線程和子線程之間的切換問題,好在NSOperation線程之間的通訊是十分簡單的。
-(void)threadCommunication{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 4; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"子線程--%@", [NSThread currentThread]);
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"主線程--%@", [NSThread currentThread]);
}
}];
}];
[queue addOperation:operation];
}
打印結果:
2020-03-19 21:48:12.051256+0800 ThreadDemo[48922:12893188] 子線程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)}
2020-03-19 21:48:14.056107+0800 ThreadDemo[48922:12893188] 子線程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)}
2020-03-19 21:48:16.059279+0800 ThreadDemo[48922:12893188] 子線程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)}
2020-03-19 21:48:18.062773+0800 ThreadDemo[48922:12893188] 子線程--<NSThread: 0x600000b5fa80>{number = 6, name = (null)}
2020-03-19 21:48:20.064401+0800 ThreadDemo[48922:12893108] 主線程--<NSThread: 0x600000bd2d00>{number = 1, name = main}
2020-03-19 21:48:22.065409+0800 ThreadDemo[48922:12893108] 主線程--<NSThread: 0x600000bd2d00>{number = 1, name = main}
3.7、NSOperation 操作依賴
NSOperation最大的亮點莫過于可以添加任務之間的依賴關系。所謂的依賴關系就是任務A需要等待任務B完成之后才能繼續執行。NSOperation提供了三個方法為任務之間設置依賴關系。
-
-(void)addDependency:(NSOperation *)op;
添加依賴,使當前操作依賴于操作 op 的完成。 -
-(void)removeDependency:(NSOperation *)op;
移除依賴,取消當前操作對操作 op 的依賴。 -
NSArray<NSOperation *> *dependencies;
在當前操作開始執行之前完成執行的所有操作對象數組。
- (void)addDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@", [NSThread currentThread]);
}
}];
// operation1依賴于operation2和operation3,則先執行operation2和operation3,然后執行operation1
[operation1 addDependency:operation2];
[operation1 addDependency:operation3];
NSArray *opList = @[operation1,operation2,operation3];
NSArray *dependencies = [operation1 dependencies];
NSLog(@"dependencies-%@",dependencies);
[queue addOperations:opList waitUntilFinished:YES];
NSLog(@"end");
}
打印結果:
2020-03-19 22:11:32.567850+0800 ThreadDemo[49369:12918472] dependencies-(
"<NSBlockOperation: 0x7ff341a06e40>",
"<NSBlockOperation: 0x7ff341a06f50>"
)
2020-03-19 22:11:34.571689+0800 ThreadDemo[49369:12918726] 3---<NSThread: 0x6000037cf180>{number = 3, name = (null)}
2020-03-19 22:11:34.571694+0800 ThreadDemo[49369:12918732] 2---<NSThread: 0x6000037fbe40>{number = 7, name = (null)}
2020-03-19 22:11:36.577098+0800 ThreadDemo[49369:12918726] 3---<NSThread: 0x6000037cf180>{number = 3, name = (null)}
2020-03-19 22:11:36.577107+0800 ThreadDemo[49369:12918732] 2---<NSThread: 0x6000037fbe40>{number = 7, name = (null)}
2020-03-19 22:11:38.582249+0800 ThreadDemo[49369:12918726] 1---<NSThread: 0x6000037cf180>{number = 3, name = (null)}
2020-03-19 22:11:40.587676+0800 ThreadDemo[49369:12918726] 1---<NSThread: 0x6000037cf180>{number = 3, name = (null)}
2020-03-19 22:11:40.587996+0800 ThreadDemo[49369:12918472] end
從上面的代碼運行結果可以看出operation2和operation3執行完成后才去執行的operation1。
3.8、NSOperation的優先級
NSOperation的另一個亮點就是NSOperation提供了queuePriority
屬性,該屬性決定了任務在隊列中執行的順序。
-
queuePriority
屬性只對同一個隊列中的任務有效。 -
queuePriority
屬性不能取代依賴關系。 - 對于進入準備就緒狀態的任務優先級高的任務優先于優先級低的任務。
- 優先級高的任務不一定會先執行,因為已經進入準備就緒狀態的任務即使是優先級低也會先執行。
- 新創建的operation對象的優先級默認是
NSOperationQueuePriorityNormal
,可以通過setQueuePriority:
方法來修改優先級。
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
- (void)addDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"1---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"2---%@", [NSThread currentThread]);
}
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"3---%@", [NSThread currentThread]);
}
}];
// operation1依賴于operation2和operation3,則先執行operation2和operation3,然后執行operation1
[operation1 addDependency:operation2];
[operation1 addDependency:operation3];
operation1.queuePriority = NSOperationQueuePriorityVeryHigh;
NSArray *opList = @[operation1,operation2,operation3];
NSArray *dependencies = [operation1 dependencies];
NSLog(@"dependencies-%@",dependencies);
[queue addOperations:opList waitUntilFinished:YES];
NSLog(@"end");
}
打印結果:
2020-03-19 22:31:15.086135+0800 ThreadDemo[49743:12937692] dependencies-(
"<NSBlockOperation: 0x7ffa6140a980>",
"<NSBlockOperation: 0x7ffa6140a760>"
)
2020-03-19 22:31:17.087052+0800 ThreadDemo[49743:12937910] 3---<NSThread: 0x6000033d1f80>{number = 5, name = (null)}
2020-03-19 22:31:17.087060+0800 ThreadDemo[49743:12937909] 2---<NSThread: 0x6000033d1780>{number = 4, name = (null)}
2020-03-19 22:31:19.087421+0800 ThreadDemo[49743:12937909] 2---<NSThread: 0x6000033d1780>{number = 4, name = (null)}
2020-03-19 22:31:19.087421+0800 ThreadDemo[49743:12937910] 3---<NSThread: 0x6000033d1f80>{number = 5, name = (null)}
2020-03-19 22:31:21.090223+0800 ThreadDemo[49743:12937910] 1---<NSThread: 0x6000033d1f80>{number = 5, name = (null)}
2020-03-19 22:31:23.092879+0800 ThreadDemo[49743:12937910] 1---<NSThread: 0x6000033d1f80>{number = 5, name = (null)}
2020-03-19 22:31:23.093183+0800 ThreadDemo[49743:12937692] end
如上代碼運行結果所示,即使將operation1的優先級設置為最高NSOperationQueuePriorityVeryHigh,operation1依然是最后執行的,那是因為operation1依賴于operation2和operation3,在operation2和operation3未執行完成之前,operation1一直是處于為就緒狀態,即使優先級最高,也不會執行。
3.9、狀態
NSOperation包含了一個十分優雅的狀態機來描述每一個操作的執行。isReady → isExecuting → isFinished
。為了替代不那么清晰的state
屬性,狀態直接由上面那些keypath
的KVO
通知決定,也就是說,當一個操作在準備好被執行的時候,它發送了一個KVO
通知給isReady
的keypath
,讓這個keypath
對應的屬性isReady
在被訪問的時候返回YES
。
每一個屬性對于其他的屬性必須是互相獨立不同的,也就是同時只可能有一個屬性返回YES,從而才能維護一個連續的狀態:
-
isReady
: 返回YES
表示操作已經準備好被執行, 如果返回NO
則說明還有其他沒有先前的相關步驟沒有完成。 -
isExecuting
: 返回YES
表示操作正在執行,反之則沒在執行。 -
isFinished
: 返回YES
表示操作執行成功或者被取消了,NSOperationQueue
只有當它管理的所有操作的isFinished
屬性全標為YES
以后操作才停止出列,也就是隊列停止運行,所以正確實現這個方法對于避免死鎖很關鍵。
3.10、其他API
-
- (void)cancel;
可取消操作,實質是標記 isCancelled 狀態。
判斷操作狀態方法 -
- (BOOL)isFinished;
判斷操作是否已經結束。 -
- (BOOL)isCancelled;
判斷操作是否已經標記為取消。 -
- (BOOL)isExecuting;
判斷操作是否正在在運行。 -
- (BOOL)isReady;
判斷操作是否處于準備就緒狀態,這個值和操作的依賴關系相關。 -
- (void)waitUntilFinished;
阻塞當前線程,直到該操作結束。可用于線程執行順序的同步。 -
- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 會在當前操作執行完畢時執行 completionBlock。 -
- (void)cancelAllOperations;
可以取消隊列的所有操作。 -
- (BOOL)isSuspended;
判斷隊列是否處于暫停狀態。 YES 為暫停狀態,NO 為恢復狀態。10.- (void)setSuspended:(BOOL)b;
可設置操作的暫停和恢復,YES 代表暫停隊列,NO 代表恢復隊列。 -
- (void)waitUntilAllOperationsAreFinished;
阻塞當前線程,直到隊列中的操作全部執行完畢。 -
- (NSUInteger)operationCount;
當前隊列中的操作數。
獲取隊列 -
+ (id)currentQueue;
獲取當前隊列,如果當前線程不是在 NSOperationQueue 上運行則返回 nil。
4、NSOperation的線程安全
和其他多線程方案一樣,解決NSOperation多線程安全問題,可以給線程加鎖,在一個線程執行該操作的時候,不允許其他線程進行操作。iOS 實現線程加鎖有很多種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock
等等各種方式。