iOS多線程:NSOperation詳解

多線程開發(fā)是日常開發(fā)任務(wù)中不可缺少的一部分救崔,在iOS開發(fā)中常用到的多線程開發(fā)技術(shù)有GCD苹粟、NSOperation飞盆、NSThread恐锦,本文主要講解多線系列文章中關(guān)于NSOperation的相關(guān)知識和使用詳解。

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

1役听、NSOperation簡介

NSOperation是蘋果公司提供的一套完整的多線程解決方案颓鲜,實(shí)際上它是基于GCD更高一層的封裝,完全面向?qū)ο蟮溆琛O鄬τ贕CD而言使用更加的簡單甜滨、代碼更具可讀性。包括網(wǎng)絡(luò)請求瘤袖、圖片壓縮在內(nèi)的諸多多線程任務(wù)案例都很好的使用了NSOperation衣摩。當(dāng)然NSOperation還需要NSOperationQueue這一重要角色配合使用。

  1. 支持在操作對象之間依賴關(guān)系捂敌,方便控制執(zhí)行順序艾扮。
  2. 支持可選的完成塊,它在操作的主要任務(wù)完成后執(zhí)行占婉。
  3. 支持使用KVO通知監(jiān)視操作執(zhí)行狀態(tài)的變化泡嘴。
  4. 支持設(shè)定操作的優(yōu)先級,從而影響它們的相對執(zhí)行順序逆济。
  5. 支持取消操作酌予,允許您在操作執(zhí)行時暫停操作。

2奖慌、NSOperation任務(wù)和隊(duì)列

2.1抛虫、NSOperation任務(wù)

和GCD一樣NSOperation同樣有任務(wù)的概念。所謂任務(wù)就是在線程中執(zhí)行的那段代碼简僧,在GCD中是放在block執(zhí)行的建椰,而在NSOperation中是在其子類 NSInvocationOperationNSBlockOperation涎劈、自定義子類中執(zhí)行的广凸。和GCD不同的是NSOperation需要NSOperationQueue的配合來實(shí)現(xiàn)多線程阅茶,NSOperation 單獨(dú)使用時是同步執(zhí)行操作蛛枚,配合 NSOperationQueue 才能實(shí)現(xiàn)異步執(zhí)行谅海。

2.2、NSOperation隊(duì)列

NSOperation中的隊(duì)列是用NSOperationQueue表示的蹦浦,用過來存放任務(wù)的隊(duì)列扭吁。

  • 不同于GCD中隊(duì)列先進(jìn)先出的原則,對于添加到NSOperationQueue隊(duì)列中的任務(wù)盲镶,首先根據(jù)任務(wù)之間的依賴關(guān)系決定任務(wù)的就緒狀態(tài)侥袜,然后進(jìn)入就緒狀態(tài)的任務(wù)由任務(wù)之間的相對優(yōu)先級決定開始執(zhí)行順序。
  • 同時NSOperationQueue提供設(shè)置最大并發(fā)任務(wù)數(shù)的途徑溉贿。
  • NSOperationQueue還提供了兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列枫吧。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺執(zhí)行宇色。

3九杂、NSOperation的基本使用

NSOperation是一個抽象類,為了做任何有用的工作宣蠕,它必須被子類化例隆。盡管這個類是抽象的,但它給了它的子類一個十分有用而且線程安全的方式來建立狀態(tài)抢蚀、優(yōu)先級镀层、依賴性和取消等的模型。NSOperation提供了三種方式來創(chuàng)建任務(wù)皿曲。
1唱逢、使用子類 NSInvocationOperation;
2屋休、使用子類 NSBlockOperation惶我;
3、自定義繼承自 NSOperation 的子類博投,通過實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來封裝操作绸贡。

下面我們先來看下NSOperation上面三種不同方式的單獨(dú)使用情況。

3.1毅哗、NSInvocationOperation

NSInvocationOperation類是NSOperation的一個具體子類听怕,當(dāng)運(yùn)行時,它調(diào)用指定對象上指定的方法虑绵。使用此類可避免為應(yīng)用程序中的每個任務(wù)定義大量自定義操作對象尿瞭。

-(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]);
    }
}

打印結(jié)果:
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}

正如上面代碼運(yùn)行的結(jié)果顯示,NSInvocationOperation單獨(dú)使用時翅睛,并沒有開啟新的線程声搁,任務(wù)都是在當(dāng)前線程中執(zhí)行的黑竞。

3.2、NSBlockOperation

NSBlockOperation類是NSOperation的一個具體子類疏旨,它充當(dāng)一個或多個塊對象的包裝很魂。該類為已經(jīng)使用操作隊(duì)列且不希望創(chuàng)建分派隊(duì)列的應(yīng)用程序提供了面向?qū)ο蟮陌b器。您還可以使用塊操作來利用操作依賴檐涝、KVO通知和其他可能與調(diào)度隊(duì)列不可用的特性遏匆。

-(void)blockOperationDemo{
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d--%@",i,[NSThread currentThread]);
        }
    }];
    [operation start];
}

打印結(jié)果:
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}

如上面代碼運(yùn)行結(jié)果所示,NSBlockOperation單獨(dú)使用時谁榜,并未開啟新的線程幅聘,任務(wù)的執(zhí)行都是在當(dāng)前線程中執(zhí)行的。

在NSBlockOperation類中還提供一個 addExecutionBlock方法窃植,這個方法可以添加一個代碼執(zhí)行塊帝蒿,當(dāng)需要執(zhí)行NSBlockOperation對象時,該對象將其所有塊提交給默認(rèn)優(yōu)先級的并發(fā)調(diào)度隊(duì)列巷怜。然后對象等待葛超,直到所有的塊完成執(zhí)行。當(dāng)最后一個塊完成執(zhí)行時丛版,操作對象將自己標(biāo)記為已完成巩掺。因此,我們可以使用塊操作來跟蹤一組執(zhí)行的塊页畦,這與使用線程連接來合并來自多個線程的結(jié)果非常相似胖替。不同之處在于,由于塊操作本身在單獨(dú)的線程上運(yùn)行豫缨,所以應(yīng)用程序的其他線程可以在等待塊操作完成的同時繼續(xù)工作独令。需要說明的一點(diǎn)是,如果添加的任務(wù)較多的話好芭,這些操作(包括 blockOperationWithBlock 中的操作)可能在不同的線程中并發(fā)執(zhí)行燃箭,這是由系統(tǒng)決定的。

- (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];
}

打印結(jié)果:
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)}

正如上面代碼運(yùn)行結(jié)果所示舍败,在調(diào)用了addExecutionBlock方法添加了組個多的任務(wù)后招狸,開啟新的線程,任務(wù)是并發(fā)執(zhí)行的邻薯,blockOperationWithBlock中的任務(wù)執(zhí)行也不是在當(dāng)前的線程執(zhí)行的裙戏。

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];
}

打印結(jié)果:
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}

從上面代碼運(yùn)行結(jié)果顯示可以看出葛作,自定義的Operation并沒有開啟新的線程,任務(wù)的執(zhí)行是在當(dāng)前的線程中執(zhí)行的猖凛。

上面講解了NSOperation單獨(dú)使用的情況赂蠢,下面我們來看看NSOperationQueue隊(duì)列配合NSOperation的使用情況。

3.4形病、添加任務(wù)到隊(duì)列

在上面就已經(jīng)提及過客年,NSOperation需要NSOperationQueue來配合使用實(shí)現(xiàn)多線程霞幅。那么我們就需要將創(chuàng)建好的NSOperation對象加載到NSOperationQueue隊(duì)列中漠吻。
NSOperationQueue提供了主隊(duì)列和自定義隊(duì)里兩種隊(duì)列,其中自定義隊(duì)列中包含了串行和并發(fā)兩種不同的功能司恳。

  • 主隊(duì)列:通過[NSOperationQueue mainQueue]方式獲取途乃,凡是添加到主隊(duì)列中的任務(wù)都會放到主線程中執(zhí)行。
  • 自定義隊(duì)列:通過[[NSOperationQueue alloc] init]方式創(chuàng)建一個隊(duì)列扔傅,凡是添加到自定義隊(duì)列中的任務(wù)會自動放到子線程中執(zhí)行耍共。
3.4.1、addOperation

調(diào)用addOperation方法將創(chuàng)建的operation對象添加到創(chuàng)建好的隊(duì)列中猎塞。

- (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];
}

打印結(jié)果:
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)}

從上面代碼運(yùn)行的結(jié)果可以看出试读,開啟了新的線程,任務(wù)是并發(fā)執(zhí)行的荠耽。如果將queue換成是mainQueue钩骇,那么任務(wù)將會在主線程中同步執(zhí)行。

3.4.2铝量、addOperations

如果任務(wù)很多時倘屹,一個個添加到隊(duì)列不免有些麻煩,那么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");
}

打印結(jié)果:
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)}

從上面代碼運(yùn)行的記過可以看出纽匙,開啟了新的線程,任務(wù)是并發(fā)執(zhí)行的拍谐。如果將queue換成是mainQueue烛缔,那么任務(wù)將會在主線程中同步執(zhí)行。

這里需要說明的一點(diǎn)的是waitUntilFinished參數(shù)轩拨,如果傳YES践瓷,則表示會等待隊(duì)列里面的任務(wù)執(zhí)行完成后才會往下執(zhí)行,也就是會阻塞線程气嫁。

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]);
        }
    }];
}

打印結(jié)果:
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)}

從上面代碼運(yùn)行的記過可以看出寸宵,開啟了新的線程崖面,任務(wù)是并發(fā)執(zhí)行的元咙。如果將queue換成是mainQueue,那么任務(wù)將會在主線程中同步執(zhí)行巫员。

3.5庶香、同步執(zhí)行&并發(fā)執(zhí)行

在前面的內(nèi)容已經(jīng)提及過,NSOperation單獨(dú)使用時默認(rèn)是系統(tǒng)同步執(zhí)行的简识,如果需要并發(fā)執(zhí)行任務(wù)赶掖,就需要NSOperationQueue的協(xié)同。那么決定是并發(fā)執(zhí)行還是同步執(zhí)行的關(guān)鍵就在于最大并發(fā)任務(wù)數(shù)maxConcurrentOperationCount七扰。

  • 默認(rèn)情況下maxConcurrentOperationCount的值是-1奢赂,并不做限制,可以并發(fā)執(zhí)行颈走,如上面提到的NSBlockOperation添加多個任務(wù)塊膳灶。
  • maxConcurrentOperationCount的值為1時,同步執(zhí)行立由。
  • maxConcurrentOperationCount的值大于1時轧钓,并發(fā)執(zhí)行。
  • maxConcurrentOperationCount的值并不是表示并發(fā)執(zhí)行的線程數(shù)量锐膜,而是在一個隊(duì)列中能夠同時執(zhí)行的任務(wù)的數(shù)量毕箍。
- (void)maxConcurrentOperationCount {
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    queue.maxConcurrentOperationCount = 1;//串行隊(duì)列
//    queue.maxConcurrentOperationCount = 4;//并發(fā)隊(duì)列
    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");
}

打印結(jié)果:
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

從上面的代碼運(yùn)行結(jié)果可以看出,開啟了新的線程道盏,任務(wù)是串行執(zhí)行的而柑。

如果將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

從上面的運(yùn)行結(jié)果可以看出捞奕,開啟了新的線程牺堰,任務(wù)是并發(fā)執(zhí)行的,而且每次執(zhí)行的任務(wù)數(shù)最大為2個颅围,那是因?yàn)槲覀冊O(shè)置了maxConcurrentOperationCount的值為2伟葫,而添加了3個任務(wù)在隊(duì)列中。

3.6院促、NSOperation線程間的通訊

多線程操作可能永遠(yuǎn)也繞不過線程間通訊這個話題筏养。通常我們將耗時的操作諸如網(wǎng)絡(luò)請求、文件上傳下載都放在子線程中執(zhí)行常拓,待執(zhí)行完成之后需要回到主線程進(jìn)行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];
}

打印結(jié)果:
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最大的亮點(diǎn)莫過于可以添加任務(wù)之間的依賴關(guān)系。所謂的依賴關(guān)系就是任務(wù)A需要等待任務(wù)B完成之后才能繼續(xù)執(zhí)行。NSOperation提供了三個方法為任務(wù)之間設(shè)置依賴關(guān)系拖陆。

  • -(void)addDependency:(NSOperation *)op; 添加依賴弛槐,使當(dāng)前操作依賴于操作 op 的完成。
  • -(void)removeDependency:(NSOperation *)op; 移除依賴依啰,取消當(dāng)前操作對操作 op 的依賴乎串。
  • NSArray<NSOperation *> *dependencies; 在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對象數(shù)組。
- (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速警,則先執(zhí)行operation2和operation3叹誉,然后執(zhí)行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");
}

打印結(jié)果:
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

從上面的代碼運(yùn)行結(jié)果可以看出operation2和operation3執(zhí)行完成后才去執(zhí)行的operation1。

3.8闷旧、NSOperation的優(yōu)先級

NSOperation的另一個亮點(diǎn)就是NSOperation提供了queuePriority屬性长豁,該屬性決定了任務(wù)在隊(duì)列中執(zhí)行的順序。

  • queuePriority屬性只對同一個隊(duì)列中的任務(wù)有效鸠匀。
  • queuePriority屬性不能取代依賴關(guān)系蕉斜。
  • 對于進(jìn)入準(zhǔn)備就緒狀態(tài)的任務(wù)優(yōu)先級高的任務(wù)優(yōu)先于優(yōu)先級低的任務(wù)逾柿。
  • 優(yōu)先級高的任務(wù)不一定會先執(zhí)行缀棍,因?yàn)橐呀?jīng)進(jìn)入準(zhǔn)備就緒狀態(tài)的任務(wù)即使是優(yōu)先級低也會先執(zhí)行。
  • 新創(chuàng)建的operation對象的優(yōu)先級默認(rèn)是NSOperationQueuePriorityNormal机错,可以通過setQueuePriority:方法來修改優(yōu)先級爬范。
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,則先執(zhí)行operation2和operation3弱匪,然后執(zhí)行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");
}

打印結(jié)果:
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

如上代碼運(yùn)行結(jié)果所示青瀑,即使將operation1的優(yōu)先級設(shè)置為最高NSOperationQueuePriorityVeryHigh,operation1依然是最后執(zhí)行的萧诫,那是因?yàn)閛peration1依賴于operation2和operation3斥难,在operation2和operation3未執(zhí)行完成之前,operation1一直是處于為就緒狀態(tài)帘饶,即使優(yōu)先級最高哑诊,也不會執(zhí)行。

3.9及刻、狀態(tài)

NSOperation包含了一個十分優(yōu)雅的狀態(tài)機(jī)來描述每一個操作的執(zhí)行镀裤。isReady → isExecuting → isFinished。為了替代不那么清晰的state屬性缴饭,狀態(tài)直接由上面那些keypathKVO通知決定暑劝,也就是說,當(dāng)一個操作在準(zhǔn)備好被執(zhí)行的時候颗搂,它發(fā)送了一個KVO通知給isReadykeypath担猛,讓這個keypath對應(yīng)的屬性isReady在被訪問的時候返回YES
每一個屬性對于其他的屬性必須是互相獨(dú)立不同的,也就是同時只可能有一個屬性返回YES傅联,從而才能維護(hù)一個連續(xù)的狀態(tài):

  • isReady: 返回YES表示操作已經(jīng)準(zhǔn)備好被執(zhí)行, 如果返回NO則說明還有其他沒有先前的相關(guān)步驟沒有完成智嚷。
  • isExecuting: 返回YES表示操作正在執(zhí)行,反之則沒在執(zhí)行纺且。
  • isFinished : 返回YES表示操作執(zhí)行成功或者被取消了盏道,NSOperationQueue只有當(dāng)它管理的所有操作的isFinished屬性全標(biāo)為YES以后操作才停止出列,也就是隊(duì)列停止運(yùn)行载碌,所以正確實(shí)現(xiàn)這個方法對于避免死鎖很關(guān)鍵猜嘱。

3.10、其他API

  1. - (void)cancel; 可取消操作嫁艇,實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)朗伶。
    判斷操作狀態(tài)方法
  2. - (BOOL)isFinished; 判斷操作是否已經(jīng)結(jié)束。
  3. - (BOOL)isCancelled; 判斷操作是否已經(jīng)標(biāo)記為取消步咪。
  4. - (BOOL)isExecuting; 判斷操作是否正在在運(yùn)行论皆。
  5. - (BOOL)isReady;判斷操作是否處于準(zhǔn)備就緒狀態(tài),這個值和操作的依賴關(guān)系相關(guān)猾漫。
  6. - (void)waitUntilFinished; 阻塞當(dāng)前線程点晴,直到該操作結(jié)束∶踔埽可用于線程執(zhí)行順序的同步粒督。
  7. - (void)setCompletionBlock:(void (^)(void))block; completionBlock 會在當(dāng)前操作執(zhí)行完畢時執(zhí)行 completionBlock。
  8. - (void)cancelAllOperations;可以取消隊(duì)列的所有操作禽翼。
  9. - (BOOL)isSuspended; 判斷隊(duì)列是否處于暫停狀態(tài)屠橄。 YES 為暫停狀態(tài),NO 為恢復(fù)狀態(tài)闰挡。10.- (void)setSuspended:(BOOL)b; 可設(shè)置操作的暫停和恢復(fù)锐墙,YES 代表暫停隊(duì)列,NO 代表恢復(fù)隊(duì)列长酗。
  10. - (void)waitUntilAllOperationsAreFinished; 阻塞當(dāng)前線程溪北,直到隊(duì)列中的操作全部執(zhí)行完畢。
  11. - (NSUInteger)operationCount; 當(dāng)前隊(duì)列中的操作數(shù)花枫。
    獲取隊(duì)列
  12. + (id)currentQueue; 獲取當(dāng)前隊(duì)列刻盐,如果當(dāng)前線程不是在 NSOperationQueue 上運(yùn)行則返回 nil。

4劳翰、NSOperation的線程安全

和其他多線程方案一樣敦锌,解決NSOperation多線程安全問題,可以給線程加鎖佳簸,在一個線程執(zhí)行該操作的時候乙墙,不允許其他線程進(jìn)行操作颖变。iOS 實(shí)現(xiàn)線程加鎖有很多種方式。@synchronized听想、 NSLock腥刹、NSRecursiveLock、NSCondition汉买、NSConditionLock衔峰、pthread_mutex、dispatch_semaphore蛙粘、OSSpinLock等等各種方式垫卤。

5、參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末出牧,一起剝皮案震驚了整個濱河市穴肘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舔痕,老刑警劉巖郭膛,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溉苛,死亡現(xiàn)場離奇詭異映皆,居然都是意外死亡幕与,警方通過查閱死者的電腦和手機(jī)士袄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門省咨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腮敌,“玉大人竖瘾,你說我怎么就攤上這事∽榈祝” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵筐骇,是天一觀的道長债鸡。 經(jīng)常有香客問我,道長铛纬,這世上最難降的妖魔是什么厌均? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮告唆,結(jié)果婚禮上棺弊,老公的妹妹穿的比我還像新娘。我一直安慰自己擒悬,他們只是感情好模她,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著懂牧,像睡著了一般侈净。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天畜侦,我揣著相機(jī)與錄音元扔,去河邊找鬼。 笑死旋膳,一個胖子當(dāng)著我的面吹牛澎语,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播验懊,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼咏连,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鲁森?” 一聲冷哼從身側(cè)響起祟滴,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歌溉,沒想到半個月后垄懂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痛垛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年草慧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匙头。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡漫谷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蹂析,到底是詐尸還是另有隱情舔示,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布电抚,位于F島的核電站惕稻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蝙叛。R本人自食惡果不足惜俺祠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望借帘。 院中可真熱鬧蜘渣,春花似錦、人聲如沸肺然。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狰挡。三九已至捂龄,卻和暖如春释涛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背倦沧。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工唇撬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人展融。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓窖认,卻偏偏與公主長得像,于是被迫代替她去往敵國和親告希。 傳聞我的和親對象是個殘疾皇子扑浸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

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