1. 程式人生 > >iOS多線程開發之NSOperation - 快上車,沒時間解釋了!

iOS多線程開發之NSOperation - 快上車,沒時間解釋了!

ddt null sleep main set ask 多個 ops exec

一、什麽是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的可以回頭看看我之前的博客。

iOS多線程開發之NSOperation - 快上車,沒時間解釋了!