一、什麼是NSOperation? NSOperation是蘋果提供的一套多線程解決方案。實際上NSOperation是基於GCD更高一層的封裝,但是比GCD更加的面向對象、代碼可讀性更高、可控性更強,很屌的是加入了操作依賴。 預設情況下,NSOperation單獨使用時只能同步執行操作,並沒有開闢 ...
一、什麼是NSOperation?
NSOperation是蘋果提供的一套多線程解決方案。實際上NSOperation是基於GCD更高一層的封裝,但是比GCD更加的面向對象、代碼可讀性更高、可控性更強,很屌的是加入了操作依賴。
預設情況下,NSOperation單獨使用時只能同步執行操作,並沒有開闢新線程的能力,只有配合NSOperationQueue才能實現非同步執行。講到這裡,我們不難發現GCD和NSOperation實現的方式很像,其實這更像是廢話,NSOperation本身就是基於GCD的封裝,NSOperation相當於GCD中的任務,而NSOperationQueue則相當於GCD中的隊列,前面《iOS多線程開發之GCD(上篇)》中已經闡述過GCD的實質:開發者要做的只是定義想執行的任務並追加到適當的Dispatch Queue中。這樣我們也可說NSOperation的本質就是:定義想執行的任務(NSOperation)並追加到適當的NSOperationQueue中。
二、NSOperation使用
1、創建任務
NSOperation是一個抽象的基類,表示一個獨立的計算單元,可以為子類提供有用且線程安全的建立狀態,優先順序,依賴和取消等操作。但它不能直接用來封裝任務,只能通過它的子類來封裝,一般的我們可以使用:NSBlockOperation、NSInvocationOperation或者定義繼承自NSOperation的子類,通過實現內部相應的方法來封裝任務。
(1)NSBlockOperation
- (void)invocationOperation{ NSLog(@"start - %@",[NSThread currentThread]); // 創建NSInvocationOperation對象 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil]; // 調用start方法開始執行操作 [op start]; NSLog(@"end - %@",[NSThread currentThread]); } - (void)testRun{ NSLog(@"invocationOperation -- %@", [NSThread currentThread]); }
執行結果:
2017-07-14 13:43:59.327 beck.wang[10248:1471363] start - <NSThread: 0x6100000614c0>{number = 1, name = main} 2017-07-14 13:43:59.328 beck.wang[10248:1471363] invocationOperation -- <NSThread: 0x6100000614c0>{number = 1, name = main} 2017-07-14 13:43:59.328 beck.wang[10248:1471363] end - <NSThread: 0x6100000614c0>{number = 1, name = main}
分析:單獨使用NSInvocationOperation的情況下,NSInvocationOperation在主線程同步執行操作,並沒有開啟新線程。
(2)NSBlockOperation
- (void)blockOperation{ NSLog(@"start - %@",[NSThread currentThread]); NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"blockOperation--%@", [NSThread currentThread]); }]; NSLog(@"end - %@",[NSThread currentThread]); [op start]; }
列印結果:
2017-07-14 13:49:25.436 beck.wang[10304:1476355] start - <NSThread: 0x6100000653c0>{number = 1, name = main} 2017-07-14 13:49:25.436 beck.wang[10304:1476355] end - <NSThread: 0x6100000653c0>{number = 1, name = main} 2017-07-14 13:49:25.436 beck.wang[10304:1476355] blockOperation--<NSThread: 0x6100000653c0>{number = 1, name = main}
分析:單獨使用NSBlockOperation的情況下,NSBlockOperation也是在主線程執行操作,沒有開啟新線程。
值得註意的是:NSBlockOperation還提供了一個方法addExecutionBlock:
,通過addExecutionBlock:
就可以為NSBlockOperation添加額外的操作,這些額外的操作就會在其他線程併發執行。
- (void)blockOperation{ NSLog(@"start - %@",[NSThread currentThread]); NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"blockOperation--%@", [NSThread currentThread]); }]; // 添加額外任務(在子線程執行) [op addExecutionBlock:^{ NSLog(@"addTask1---%@", [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog(@"addTask2---%@", [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog(@"addTask3---%@", [NSThread currentThread]); }]; NSLog(@"end - %@",[NSThread currentThread]); [op start]; }
列印結果:
2017-07-14 13:57:02.009 beck.wang[10351:1482603] start - <NSThread: 0x60000007cdc0>{number = 1, name = main} 2017-07-14 13:57:02.009 beck.wang[10351:1482603] end - <NSThread: 0x60000007cdc0>{number = 1, name = main} 2017-07-14 13:57:02.010 beck.wang[10351:1482603] blockOperation--<NSThread: 0x60000007cdc0>{number = 1, name = main} 2017-07-14 13:57:02.010 beck.wang[10351:1482642] addTask1---<NSThread: 0x618000260e00>{number = 3, name = (null)} 2017-07-14 13:57:02.010 beck.wang[10351:1482645] addTask3---<NSThread: 0x600000263200>{number = 5, name = (null)} 2017-07-14 13:57:02.010 beck.wang[10351:1482643] addTask2---<NSThread: 0x610000264600>{number = 4, name = (null)}
分析:blockOperationWithBlock任務在主線程中執行,addExecutionBlock的任務在新開線程中執行。
(3)自定義NSOperation子類--重寫main方法即可
.h
@interface ZTOperation : NSOperation @end
.m
@implementation ZTOperation - (void)main{ // 在這裡可以自定義任務 NSLog(@"ZTOperation--%@",[NSThread currentThread]); } @end
ViewController
ZTOperation *zt = [[ZTOperation alloc] init];
[zt start];
列印結果:
2017-07-14 14:05:58.824 beck.wang[10389:1490955] ZTOperation--<NSThread: 0x60000007a940>{number = 1, name = main}
分析:任務在主線程中執行,不開啟新線程。
2、創建隊列
NSOperationQueue
一共有兩種隊列:主隊列、其他隊列。其中其他隊列同時包含了串列、併發功能,通過設置最大併發數maxConcurrentOperationCount來實現串列、併發!
(1)主隊列 -- 任務在主線程中執行
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
(2)其他隊列 -- 任務在子線程中執行
NSOperationQueue *elseQueue = [[NSOperationQueue alloc] init];
3、NSOperation + NSOperationQueue (任務追加到隊列)
// 添加單個操作: - (void)addOperation:(NSOperation *)op; // 添加多個操作: - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0); // 添加block操作: - (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
代碼示例:
- (void)addOperationToQueue { NSLog(@"start - %@",[NSThread currentThread]); // 創建隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 創建NSInvocationOperation NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testRun) object:nil]; // 創建NSBlockOperation NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"task002 -- %@", [NSThread currentThread]); }]; // 添加操作到隊列中: addOperation: [queue addOperation:op1]; [queue addOperation:op2]; // 添加操作到隊列中:addOperationWithBlock: [queue addOperationWithBlock:^{ NSLog(@"task003-----%@", [NSThread currentThread]); }]; NSLog(@"end - %@",[NSThread currentThread]); } - (void)testRun{ NSLog(@"task001 -- %@", [NSThread currentThread]); }
列印結果:
2017-07-14 14:39:51.669 beck.wang[10536:1516641] start - <NSThread: 0x610000077640>{number = 1, name = main} 2017-07-14 14:39:51.670 beck.wang[10536:1516641] end - <NSThread: 0x610000077640>{number = 1, name = main} 2017-07-14 14:39:51.670 beck.wang[10536:1516686] task003-----<NSThread: 0x600000077200>{number = 3, name = (null)} 2017-07-14 14:39:51.670 beck.wang[10536:1516689] task002 -- <NSThread: 0x61800007e080>{number = 5, name = (null)} 2017-07-14 14:39:51.670 beck.wang[10536:1516687] task001 -- <NSThread: 0x61000007e1c0>{number = 4, name = (null)}
分析:開啟新線程,併發執行。
三、NSOperationQueue管理
1、隊列的取消、暫停、恢復
- (void)cancel; NSOperation提供的方法,可取消單個操作
- (void)cancelAllOperations; NSOperationQueue提供的方法,可以取消隊列的所有操作
- (void)setSuspended:(BOOL)b; 可設置任務的暫停和恢復,YES代表暫停隊列,NO代表恢復隊列
- (BOOL)isSuspended; 判斷暫停狀態
暫停或取消並不能使正在執行的操作立即暫停或取消,而是當前操作執行完後不再執行新的操作。兩者的區別在於暫停操作之後還可以恢復操作,繼續向下執行;而取消操作之後,所有的操作就清空了,無法再接著執行剩下的操作。
2、最大併發數 maxConcurrentOperationCount
maxConcurrentOperationCount = - 1 表示不限制,預設併發執行;
maxConcurrentOperationCount = 1 表示最大併發數為1,串列執行;
maxConcurrentOperationCount > ([count] > =1) 表示併發執行,min[count,系統限制]。
代碼示例:
- (void)operationQueue { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 設置最大併發操作數 // queue.maxConcurrentOperationCount = - 1; // 併發執行 // queue.maxConcurrentOperationCount = 1; // 同步執行 queue.maxConcurrentOperationCount = 2; // 併發執行 [queue addOperationWithBlock:^{ NSLog(@"task1-----%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ NSLog(@"task2-----%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ NSLog(@"task3-----%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ NSLog(@"task4-----%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ NSLog(@"task5-----%@", [NSThread currentThread]); }]; [queue addOperationWithBlock:^{ NSLog(@"task6-----%@", [NSThread currentThread]); }]; }
列印結果:
// queue.maxConcurrentOperationCount = - 1 2017-07-14 15:28:39.554 beck.wang[10772:1557342] task2-----<NSThread: 0x61800006d340>{number = 4, name = (null)} 2017-07-14 15:28:39.554 beck.wang[10772:1557358] task3-----<NSThread: 0x6080000751c0>{number = 5, name = (null)} 2017-07-14 15:28:39.554 beck.wang[10772:1557359] task4-----<NSThread: 0x610000071c00>{number = 6, name = (null)} 2017-07-14 15:28:39.554 beck.wang[10772:1557339] task5-----<NSThread: 0x60000006ea40>{number = 7, name = (null)} 2017-07-14 15:28:39.554 beck.wang[10772:1557340] task1-----<NSThread: 0x608000073500>{number = 3, name = (null)} 2017-07-14 15:28:39.554 beck.wang[10772:1557360] task6-----<NSThread: 0x610000071c80>{number = 8, name = (null)} // 分析:線程數為6,併發執行 -----------------------------------分割線---------------------------------------------- // queue.maxConcurrentOperationCount = 1 2017-07-14 15:27:04.365 beck.wang[10743:1555231] task1-----<NSThread: 0x60800007c880>{number = 3, name = (null)} 2017-07-14 15:27:04.365 beck.wang[10743:1555231] task2-----<NSThread: 0x60800007c880>{number = 3, name = (null)} 2017-07-14 15:27:04.365 beck.wang[10743:1555231] task3-----<NSThread: 0x60800007c880>{number = 3, name = (null)} 2017-07-14 15:27:04.365 beck.wang[10743:1555231] task4-----<NSThread: 0x60800007c880>{number = 3, name = (null)} 2017-07-14 15:27:04.366 beck.wang[10743:1555231] task5-----<NSThread: 0x60800007c880>{number = 3, name = (null)} 2017-07-14 15:27:04.366 beck.wang[10743:1555231] task6-----<NSThread: 0x60800007c880>{number = 3, name = (null)} // 分析:線程個數為1,同步執行 -----------------------------------分割線---------------------------------------------- // queue.maxConcurrentOperationCount = 2 2017-07-14 15:18:26.162 beck.wang[10715:1548342] task2-----<NSThread: 0x608000079740>{number = 4, name = (null)} 2017-07-14 15:18:26.162 beck.wang[10715:1548344] task1-----<NSThread: 0x6100000770c0>{number = 3, name = (null)} 2017-07-14 15:18:26.162 beck.wang[10715:1548342] task4-----<NSThread: 0x608000079740>{number = 4, name = (null)} 2017-07-14 15:18:26.162 beck.wang[10715:1548344] task3-----<NSThread: 0x6100000770c0>{number = 3, name = (null)} 2017-07-14 15:18:26.162 beck.wang[10715:1548342] task5-----<NSThread: 0x608000079740>{number = 4, name = (null)} 2017-07-14 15:18:26.163 beck.wang[10715:1548344] task6-----<NSThread: 0x6100000770c0>{number = 3, name = (null)} // 分析:線程個數為2,併發執行
很明顯,通過設置maxConcurrentOperationCount就能實現併發、串列功能是不是比GCD輕鬆多了!
3、操作依賴
NSOperation中我們可以為操作分解為若幹個小的任務,通過添加他們之間的依賴關係進行操作,這個經常用到!這也是NSOperation吸引人的地方,不需要像GCD那樣使用複雜的代碼實現,addDependency就可以搞定!
- (void)addDependency { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ sleep(2); NSLog(@"task1-----%@", [NSThread currentThread]); }]; NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"task2-----%@", [NSThread currentThread]); }]; NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"task3-----%@", [NSThread currentThread]); }]; // op2依賴於op1 執行順序op1->op2 必須放在[添加操作隊列]之前 [op2 addDependency:op1]; // 忌迴圈依賴 op2已經依賴於op1,切不可再讓op1依賴於op2,形成迴圈依賴 //[op1 addDependency:op2]; // 添加操作隊列 [queue addOperation:op1]; [queue addOperation:op2]; [queue addOperation:op3]; }列印結果:
2017-07-14 15:46:02.011 beck.wang[10854:1571574] task3-----<NSThread: 0x61800006d740>{number = 3, name = (null)} 2017-07-14 15:46:04.085 beck.wang[10854:1571596] task1-----<NSThread: 0x60000006f040>{number = 4, name = (null)} 2017-07-14 15:46:04.085 beck.wang[10854:1571574] task2-----<NSThread: 0x61800006d740>{number = 3, name = (null)}
分析:task2一定在task1後面執行,因為執行task1前設置了線程等待2s,所有task3最早執行。
4、操作優先順序
NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8
5、操作的監聽
可以監聽一個操作是否執行完畢,如下載圖片,需要在下載第一張圖片後才能下載第二張圖片,這裡就可以設置監聽。
- (void)addListing{ NSOperationQueue *queue=[[NSOperationQueue alloc]init]; NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<3; i++) { NSLog(@"下載圖片1-%@",[NSThread currentThread]); } }]; // 監聽操作的執行完畢 operation.completionBlock=^{ // 繼續進行下載圖片操作 NSLog(@"--下載圖片2--"); }; [queue addOperation:operation]; }
執行結果:
2017-07-14 16:21:43.833 beck.wang[10930:1597954] 下載圖片1-<NSThread: 0x61800007a340>{number = 3, name = (null)} 2017-07-14 16:21:43.834 beck.wang[10930:1597954] 下載圖片1-<NSThread: 0x61800007a340>{number = 3, name = (null)} 2017-07-14 16:21:43.834 beck.wang[10930:1597954] 下載圖片1-<NSThread: 0x61800007a340>{number = 3, name = (null)} 2017-07-14 16:21:43.834 beck.wang[10930:1597955] --下載圖片2--
分析:下載圖片1完成後才會執行下載圖片2,這裡類似知識點3中的添加依賴。
留在最後的話:多線程不只是有GCD!如果你還沒有用過NSOperation,還說什麼呢?趕緊操練起來!當然他們各有各的使用場景,存在即合理!iOS多線程的三種技術GCD、NSThread、NSOperation就都介紹完了,需要瞭解 GCD、NSThread的可以回頭看看我之前的博客。