iOS多線程:NSOperation詳解

多線程開發是平常開發任務中不可缺乏的一部分,在iOS開發中經常使用到的多線程開發技術有GCD、NSOperation、NSThread,本文主要講解多線系列文章中關於NSOperation的相關知識和使用詳解。html

  1. iOS多線程:GCD詳解
  2. iOS多線程:NSOperation詳解

一、NSOperation簡介

NSOperation是蘋果公司提供的一套完整的多線程解決方案,實際上它是基於GCD更高一層的封裝,徹底面向對象。相對於GCD而言使用更加的簡單、代碼更具可讀性。包括網絡請求、圖片壓縮在內的諸多多線程任務案例都很好的使用了NSOperation。固然NSOperation還須要NSOperationQueue這一重要角色配合使用。數組

  1. 支持在操做對象之間依賴關係,方便控制執行順序。
  2. 支持可選的完成塊,它在操做的主要任務完成後執行。
  3. 支持使用KVO通知監視操做執行狀態的變化。
  4. 支持設定操做的優先級,從而影響它們的相對執行順序。
  5. 支持取消操做,容許您在操做執行時暫停操做。

二、NSOperation任務和隊列

2.一、NSOperation任務

和GCD同樣NSOperation一樣有任務的概念。所謂任務就是在線程中執行的那段代碼,在GCD中是放在block執行的,而在NSOperation中是在其子類 NSInvocationOperationNSBlockOperation自定義子類中執行的。和GCD不一樣的是NSOperation須要NSOperationQueue的配合來實現多線程,NSOperation 單獨使用時是同步執行操做,配合 NSOperationQueue 才能實現異步執行。安全

2.二、NSOperation隊列

NSOperation中的隊列是用NSOperationQueue表示的,用過來存聽任務的隊列。bash

  • 不一樣於GCD中隊列先進先出的原則,對於添加到NSOperationQueue隊列中的任務,首先根據任務之間的依賴關係決定任務的就緒狀態,而後進入就緒狀態的任務由任務之間的相對優先級決定開始執行順序。
  • 同時NSOperationQueue提供設置最大併發任務數的途徑。
  • NSOperationQueue還提供了兩種不一樣類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在後臺執行。

三、NSOperation的基本使用

NSOperation是一個抽象類,爲了作任何有用的工做,它必須被子類化。儘管這個類是抽象的,但它給了它的子類一個十分有用並且線程安全的方式來創建狀態、優先級、依賴性和取消等的模型。NSOperation提供了三種方式來建立任務。 一、使用子類 NSInvocationOperation; 二、使用子類 NSBlockOperation; 三、自定義繼承自 NSOperation 的子類,經過實現內部相應的方法來封裝操做。網絡

下面咱們先來看下NSOperation上面三種不一樣方式的單獨使用狀況。多線程

3.一、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}app

正如上面代碼運行的結果顯示,NSInvocationOperation單獨使用時,並無開啓新的線程,任務都是在當前線程中執行的。異步

3.二、NSBlockOperation

NSBlockOperation類是NSOperation的一個具體子類,它充當一個或多個塊對象的包裝。該類爲已經使用操做隊列且不但願建立分派隊列的應用程序提供了面向對象的包裝器。您還可使用塊操做來利用操做依賴、KVO通知和其餘可能與調度隊列不可用的特性。ide

-(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.三、自定義 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.四、添加任務到隊列

在上面就已經說起過,NSOperation須要NSOperationQueue來配合使用實現多線程。那麼咱們就須要將建立好的NSOperation對象加載到NSOperationQueue隊列中。 NSOperationQueue提供了主隊列和自定義隊裏兩種隊列,其中自定義隊列中包含了串行和併發兩種不一樣的功能。

  • 主隊列:經過[NSOperationQueue mainQueue]方式獲取,凡是添加到主隊列中的任務都會放到主線程中執行。
  • 自定義隊列:經過[[NSOperationQueue alloc] init]方式建立一個隊列,凡是添加到自定義隊列中的任務會自動放到子線程中執行。
3.4.一、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.二、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.三、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.五、同步執行&併發執行

在前面的內容已經說起過,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.六、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.七、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.八、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.九、狀態

NSOperation包含了一個十分優雅的狀態機來描述每個操做的執行。isReady → isExecuting → isFinished。爲了替代不那麼清晰的state屬性,狀態直接由上面那些keypathKVO通知決定,也就是說,當一個操做在準備好被執行的時候,它發送了一個KVO通知給isReadykeypath,讓這個keypath對應的屬性isReady在被訪問的時候返回YES。 每個屬性對於其餘的屬性必須是互相獨立不一樣的,也就是同時只可能有一個屬性返回YES,從而才能維護一個連續的狀態:

  • isReady: 返回YES表示操做已經準備好被執行, 若是返回NO則說明還有其餘沒有先前的相關步驟沒有完成。
  • isExecuting: 返回YES表示操做正在執行,反之則沒在執行。
  • isFinished : 返回YES表示操做執行成功或者被取消了,NSOperationQueue只有當它管理的全部操做的isFinished屬性全標爲YES之後操做才中止出列,也就是隊列中止運行,因此正確實現這個方法對於避免死鎖很關鍵。

3.十、其餘API

  1. - (void)cancel; 可取消操做,實質是標記 isCancelled 狀態。 判斷操做狀態方法
  2. - (BOOL)isFinished; 判斷操做是否已經結束。
  3. - (BOOL)isCancelled; 判斷操做是否已經標記爲取消。
  4. - (BOOL)isExecuting; 判斷操做是否正在在運行。
  5. - (BOOL)isReady;判斷操做是否處於準備就緒狀態,這個值和操做的依賴關係相關。
  6. - (void)waitUntilFinished; 阻塞當前線程,直到該操做結束。可用於線程執行順序的同步。
  7. - (void)setCompletionBlock:(void (^)(void))block; completionBlock 會在當前操做執行完畢時執行 completionBlock。
  8. - (void)cancelAllOperations;能夠取消隊列的全部操做。
  9. - (BOOL)isSuspended; 判斷隊列是否處於暫停狀態。 YES 爲暫停狀態,NO 爲恢復狀態。10.- (void)setSuspended:(BOOL)b; 可設置操做的暫停和恢復,YES 表明暫停隊列,NO 表明恢復隊列。
  10. - (void)waitUntilAllOperationsAreFinished; 阻塞當前線程,直到隊列中的操做所有執行完畢。
  11. - (NSUInteger)operationCount; 當前隊列中的操做數。 獲取隊列
  12. + (id)currentQueue; 獲取當前隊列,若是當前線程不是在 NSOperationQueue 上運行則返回 nil。

四、NSOperation的線程安全

和其餘多線程方案同樣,解決NSOperation多線程安全問題,能夠給線程加鎖,在一個線程執行該操做的時候,不容許其餘線程進行操做。iOS 實現線程加鎖有不少種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock等等各類方式。

五、參考資料

相關文章
相關標籤/搜索