多線程開發(fā)是日常開發(fā)任務(wù)中不可缺少的一部分救崔,在iOS開發(fā)中常用到的多線程開發(fā)技術(shù)有
GCD苹粟、NSOperation飞盆、NSThread
恐锦,本文主要講解多線系列文章中關(guān)于NSOperation
的相關(guān)知識和使用詳解。
1役听、NSOperation簡介
NSOperation
是蘋果公司提供的一套完整的多線程解決方案颓鲜,實(shí)際上它是基于GCD
更高一層的封裝,完全面向?qū)ο蟮溆琛O鄬τ贕CD而言使用更加的簡單甜滨、代碼更具可讀性。包括網(wǎng)絡(luò)請求瘤袖、圖片壓縮在內(nèi)的諸多多線程任務(wù)案例都很好的使用了NSOperation衣摩。當(dāng)然NSOperation還需要NSOperationQueue
這一重要角色配合使用。
- 支持在操作對象之間依賴關(guān)系捂敌,方便控制執(zhí)行順序艾扮。
- 支持可選的完成塊,它在操作的主要任務(wù)完成后執(zhí)行占婉。
- 支持使用KVO通知監(jiān)視操作執(zhí)行狀態(tài)的變化泡嘴。
- 支持設(shè)定操作的優(yōu)先級,從而影響它們的相對執(zhí)行順序逆济。
- 支持取消操作酌予,允許您在操作執(zhí)行時暫停操作。
2奖慌、NSOperation任務(wù)和隊(duì)列
2.1抛虫、NSOperation任務(wù)
和GCD一樣NSOperation同樣有任務(wù)的概念。所謂任務(wù)就是在線程中執(zhí)行的那段代碼简僧,在GCD中是放在block執(zhí)行的建椰,而在NSOperation中是在其子類 NSInvocationOperation
、NSBlockOperation
涎劈、自定義子類
中執(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)直接由上面那些keypath
的KVO
通知決定暑劝,也就是說,當(dāng)一個操作在準(zhǔn)備好被執(zhí)行的時候颗搂,它發(fā)送了一個KVO
通知給isReady
的keypath
担猛,讓這個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
-
- (void)cancel;
可取消操作嫁艇,實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)朗伶。
判斷操作狀態(tài)方法 -
- (BOOL)isFinished;
判斷操作是否已經(jīng)結(jié)束。 -
- (BOOL)isCancelled;
判斷操作是否已經(jīng)標(biāo)記為取消步咪。 -
- (BOOL)isExecuting;
判斷操作是否正在在運(yùn)行论皆。 -
- (BOOL)isReady;
判斷操作是否處于準(zhǔn)備就緒狀態(tài),這個值和操作的依賴關(guān)系相關(guān)猾漫。 -
- (void)waitUntilFinished;
阻塞當(dāng)前線程点晴,直到該操作結(jié)束∶踔埽可用于線程執(zhí)行順序的同步粒督。 -
- (void)setCompletionBlock:(void (^)(void))block;
completionBlock 會在當(dāng)前操作執(zhí)行完畢時執(zhí)行 completionBlock。 -
- (void)cancelAllOperations;
可以取消隊(duì)列的所有操作禽翼。 -
- (BOOL)isSuspended;
判斷隊(duì)列是否處于暫停狀態(tài)屠橄。 YES 為暫停狀態(tài),NO 為恢復(fù)狀態(tài)闰挡。10.- (void)setSuspended:(BOOL)b;
可設(shè)置操作的暫停和恢復(fù)锐墙,YES 代表暫停隊(duì)列,NO 代表恢復(fù)隊(duì)列长酗。 -
- (void)waitUntilAllOperationsAreFinished;
阻塞當(dāng)前線程溪北,直到隊(duì)列中的操作全部執(zhí)行完畢。 -
- (NSUInteger)operationCount;
當(dāng)前隊(duì)列中的操作數(shù)花枫。
獲取隊(duì)列 -
+ (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
等等各種方式垫卤。