這一篇文章主要介紹iOS中實現(xiàn)多線程的第四種方案:NSOperation和NSOperationQueue,在日常的開發(fā)過程中也是用得相對較多的一種方式。
1、NSOperation和NSOperationQueue概念
NSOperation和NSOperationQueue是蘋果對GCD的更高一層的封裝,完全面向?qū)ο?。了解過GCD之后再來看,可以知道NSOperation對應(yīng)的GCD中的任務(wù)概念,而NSOperationQueue對應(yīng)隊列。
那么既然已經(jīng)有了GCD,而且效率很高,使用也很方便,為什么還需要NSOperation呢?相較于GCD,NSOperation和NSOperationQueue有哪些不一樣的特點?
2、 NSOperation和GCD比較
看一幅圖:
GCD的底層是C語言寫的,我們知道越往底層效率越高,執(zhí)行和操作更簡單。NSOperation底層實際也是通過GCD實現(xiàn)的,而NSOperation是對GCD更高層次的抽象,這是他們之間最本質(zhì)的區(qū)別。
NSOperation可以直接設(shè)置兩個NSOperation之間的依賴,一個任務(wù)依賴另一個任務(wù)執(zhí)行,這種具有依賴關(guān)系在開發(fā)中也很常見。GCD無法直接設(shè)置依賴關(guān)系,不過可以通過dispatch_barrier_async來實現(xiàn)類似效果。
NSOperation很容易判斷任務(wù)當(dāng)前的狀態(tài)(比如是否執(zhí)行,是否取消),對此GCD無法通過KVO進行判斷。
NSOperation同時也提供了設(shè)置自身任務(wù)優(yōu)先級的方法,但是優(yōu)先級高的不一定先執(zhí)行,需要參照一定條件,GCD只能設(shè)置隊列的優(yōu)先級,不能給執(zhí)行的任務(wù)設(shè)置優(yōu)先級。
NSOperation是一個抽象類,在開發(fā)中常用的兩個子類是NSInvocationOperation和NSBlockOperation。另外如果需要更多需求,我們可以自定義NSOperation,更加靈活。GCD執(zhí)行任務(wù)可以自由組裝,沒有繼承那么高的代碼復(fù)用度。
3、NSOperation的子類
由于NSOperation是一個抽象類,它本身并不能做任何操作,需要通過它的兩個子類來完成一些特定的任務(wù)。
- NSInvocationOperation
使用NSInvocationOperation創(chuàng)建任務(wù)時代碼如下:
// NSInvocationOperation
- (void)nsInvocationOperationTest
{
// NSInvocationOperation添加在當(dāng)前線程執(zhí)行的方法
NSInvocationOperation *invOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethod) object:nil];
// 開始執(zhí)行
[invOperation start];
}
/ NSInvocationOperation執(zhí)行的方法
- (void)invocationOperationMethod
{
NSLog(@"currentThread:%@", [NSThread currentThread]);
}
這個方法是添加任務(wù)到當(dāng)前線程,如果沒有指定任務(wù)執(zhí)行所在的線程,那么一般該任務(wù)是在主線程中執(zhí)行。
2019-06-22 22:54:20.377189+0800 Thread-Test[10082:146308] currentThread:<NSThread: 0x60000285e680>{number = 1, name = main}
如果想要開辟一個新線程來執(zhí)行指定的任務(wù),就需要配合NSThread的方法:
[NSThread detachNewThreadSelector:@selector(nsInvocationOperationTest) toTarget:self withObject:nil];
- NSBlockOperation
如果使用NSBlockOperation,創(chuàng)建任務(wù)直接在緊鄰的Block中執(zhí)行,不需要創(chuàng)建新的方法:
// NSBlockOperation
- (void)blockOperationTest
{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block-current-thread:%@", [NSThread currentThread]);
}];
[blockOperation start];
}
其中從打印結(jié)果來看,NSBlockOperation創(chuàng)建的任務(wù)也是在當(dāng)前線程中執(zhí)行。
2019-06-22 23:04:12.356336+0800 Thread-Test[10184:150775] block-current-thread:<NSThread: 0x600000e9d440>{number = 1, name = main}
不過NSBlockOperation還有另外一個方法,可以追加更多的操作,那就是addExecutionBlock。
// NSBlockOperation
NSBlockOperation *blockOperaton = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"要執(zhí)行的任務(wù):%@", [NSThread currentThread]);
}];
// 添加多個執(zhí)行任務(wù)
[blockOperaton addExecutionBlock:^{
NSLog(@"新增任務(wù)1:%@", [NSThread currentThread]);
}];
[blockOperaton addExecutionBlock:^{
NSLog(@"新增任務(wù)2:%@", [NSThread currentThread]);
}];
[blockOperaton addExecutionBlock:^{
NSLog(@"新增任務(wù)3:%@", [NSThread currentThread]);
}];
打印結(jié)果:
2019-06-22 23:23:40.314257+0800 Thread-Test[10310:156634] 要執(zhí)行的任務(wù):<NSThread: 0x6000000d0f80>{number = 1, name = main}
2019-06-22 23:23:40.314270+0800 Thread-Test[10310:156667] 新增任務(wù)1:<NSThread: 0x600000087600>{number = 4, name = (null)}
2019-06-22 23:23:40.314290+0800 Thread-Test[10310:156673] 新增任務(wù)2:<NSThread: 0x60000008d580>{number = 5, name = (null)}
2019-06-22 23:23:40.314306+0800 Thread-Test[10310:156669] 新增任務(wù)3:<NSThread: 0x60000008d440>{number = 6, name = (null)}
2019-06-22 23:23:40.314399+0800 Thread-Test[10310:156634] 新增任務(wù)4:<NSThread: 0x6000000d0f80>{number = 1, name = main}
從打印結(jié)果可以看出,這幾個任務(wù)并不是都在同一個線程執(zhí)行的,而且可以看出是并發(fā)執(zhí)行的。至于會通過多少個并發(fā)線程來執(zhí)行這段任務(wù),是由系統(tǒng)來決定的。
- 自定義NSOperation
如果NSOperation的兩個子類不能滿足我們的開發(fā)需求,就可以自定義一個繼承于NSOperation的子類,讓其在創(chuàng)建時就默認(rèn)執(zhí)行某些任務(wù)。
自定義NSOperation有兩種類型,一種是非并發(fā)的,定義比較簡單,另一種是并發(fā)的,相對步驟要多一點。
先來看一下非并發(fā)自定義NSOperation,按照蘋果官方的說明,定義做如下步驟:
1、一個自定義的初始化方法(不定義也能實現(xiàn))
2、重寫main函數(shù)
JCNonconcurrentOperation.h文件:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JCNonconcurrentOperation : NSOperation
- (id)initWithFlag:(NSInteger)flag;
@end
NS_ASSUME_NONNULL_END
JCNonconcurrentOperation.m:
#import "JCNonconcurrentOperation.h"
@interface JCNonconcurrentOperation()
{
NSInteger _flag;
}
@end
@implementation JCNonconcurrentOperation
- (id)initWithFlag:(NSInteger)flag {
if (self = [super init])
{
_flag = flag;
}
return self;
}
-(void)main {
@try {
// 要運行的任務(wù)
NSLog(@"falg - %ld-JC-NonconcurentOperation - %@", _flag, [NSThread currentThread]);
}
@catch(...) {
// Do not rethrow exceptions.
}
}
@end
在這里為了便于比較輸出結(jié)果,我們在初始化的地方定義傳遞一個flag的參數(shù),在其他地方調(diào)用這個非并發(fā)的自定義NSOperation時按照如下代碼:
// 自定義非并發(fā)NSOperation測試方法
- (void)nonconcurrentOperationTest
{
// JCNonconcurrentOperation *nonConcurrentOp = [[JCNonconcurrentOperation alloc] initWithData:@""];
// [nonConcurrentOp start];
JCNonconcurrentOperation *nonConcurrentOp = [[JCNonconcurrentOperation alloc] initWithFlag:0];
[nonConcurrentOp start];
NSLog(@"任務(wù)0執(zhí)行完");
JCNonconcurrentOperation *nonConcurrentOp1 = [[JCNonconcurrentOperation alloc] initWithFlag:1];
[nonConcurrentOp1 start];
NSLog(@"任務(wù)1執(zhí)行完");
JCNonconcurrentOperation *nonConcurrentOp2 = [[JCNonconcurrentOperation alloc] initWithFlag:2];
[nonConcurrentOp2 start];
NSLog(@"任務(wù)2執(zhí)行完");
}
打印結(jié)果:
2019-06-23 14:43:57.171927+0800 Thread-Test[15078:331719] falg - 0-JC-NonconcurentOperation - <NSThread: 0x600002d1e900>{number = 1, name = main}
2019-06-23 14:43:57.172135+0800 Thread-Test[15078:331719] 任務(wù)0執(zhí)行完
2019-06-23 14:43:57.172311+0800 Thread-Test[15078:331719] falg - 1-JC-NonconcurentOperation - <NSThread: 0x600002d1e900>{number = 1, name = main}
2019-06-23 14:43:57.172428+0800 Thread-Test[15078:331719] 任務(wù)1執(zhí)行完
2019-06-23 14:43:57.172551+0800 Thread-Test[15078:331719] falg - 2-JC-NonconcurentOperation - <NSThread: 0x600002d1e900>{number = 1, name = main}
2019-06-23 14:43:57.172657+0800 Thread-Test[15078:331719] 任務(wù)2執(zhí)行完
輸出結(jié)果可以看出三個任務(wù)是串行的,一個執(zhí)行完了才會執(zhí)行下一個。
再來看一下怎樣自定義一個并發(fā)的NSOperation,同樣參照蘋果官方文檔可知,實現(xiàn)的步驟如下:
1、必須重寫start方法
2、重寫了start方法后,main方法重寫可選,但是一般在自定義時,還是習(xí)慣把需要執(zhí)行的任務(wù)放在main函數(shù)中。
3、必須手動管理isExecuting和isFinished狀態(tài),主要作用是在線程狀態(tài)改變時,產(chǎn)生適當(dāng)?shù)腒VO通知。
4、 重寫isConcurrent的get方法,并返回YES。
我們手動再創(chuàng)建一個繼承自NSOperation的類。
在JCConcurrentOperation.h中:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JCConcurrentOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (id)initWithFlag:(NSInteger)flag;
- (void)completeOperation;
@end
NS_ASSUME_NONNULL_END
在JCConcurrentOperation.m中:
#import "JCConcurrentOperation.h"
@interface JCConcurrentOperation()
{
NSInteger _flag;
}
@end
@implementation JCConcurrentOperation
- (id)initWithFlag:(NSInteger)flag {
if(self = [super init])
{
_flag = flag;
executing = NO;
finished = NO;
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (void)start {
// 第一步就要檢測是否被取消了,如果取消了,要實現(xiàn)相應(yīng)的KVO
if ([self isCancelled])
{
// 如果任務(wù)取消了,要通過KVO設(shè)置isFinished
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// 如果任務(wù)沒有取消,需要設(shè)置isExecuting,表示正在執(zhí)行
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
@try {
@autoreleasepool {
//在這里定義自己的并發(fā)任務(wù)
NSThread *thread = [NSThread currentThread];
NSLog(@"flag - %ld自定義并發(fā)操作NSOperation - %@", _flag, thread);
// 任務(wù)執(zhí)行完成后要實現(xiàn)相應(yīng)的KVO
[self completeOperation];
}
} @catch (NSException *exception) {
} @finally {
}
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
@end
來調(diào)用這個自定義的并發(fā)NSOperation:
JCConcurrentOperation *concurrentop0 = [[JCConcurrentOperation alloc] initWithFlag:0];
[concurrentop0 start];
NSLog(@"con-任務(wù)1完成");
JCConcurrentOperation *concurrentOp1 = [[JCConcurrentOperation alloc] initWithFlag:1];
[concurrentOp1 start];
NSLog(@"con-任務(wù)2完成");
JCConcurrentOperation *concurrentOp2 = [[JCConcurrentOperation alloc] initWithFlag:2];
[concurrentOp2 start];
NSLog(@"con-任務(wù)3完成");
打印結(jié)果:
2019-06-23 15:08:08.716065+0800 Thread-Test[15254:340594] con-任務(wù)1完成
2019-06-23 15:08:08.716562+0800 Thread-Test[15254:340594] con-任務(wù)2完成
2019-06-23 15:08:08.716773+0800 Thread-Test[15254:340662] flag - 1自定義并發(fā)操作NSOperation - <NSThread: 0x600002629b80>{number = 4, name = (null)}
2019-06-23 15:08:08.716682+0800 Thread-Test[15254:340661] flag - 0自定義并發(fā)操作NSOperation - <NSThread: 0x600002629b00>{number = 3, name = (null)}
2019-06-23 15:08:08.716832+0800 Thread-Test[15254:340594] con-任務(wù)3完成
2019-06-23 15:08:08.719572+0800 Thread-Test[15254:340663] flag - 2自定義并發(fā)操作NSOperation - <NSThread: 0x60000260f000>{number = 5, name = (null)}
比較上面兩種自定義的方式輸出的結(jié)果就能很明顯的看出任務(wù)執(zhí)行是并發(fā)還是非并發(fā)了。
4、NSOperationQueue創(chuàng)建隊列
與GCD類似,很多時候我們使用NSOperation的時候,其中的任務(wù)也是會放在隊列中執(zhí)行的,而NSOperation中的隊列就是由NSOperationQueue來管理和創(chuàng)建的。
NSOperationQueue有兩種隊列分類,一個是主隊列,另外一個是除了主隊列的自定義隊列。
- 主隊列的創(chuàng)建方式:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
創(chuàng)建幾個任務(wù)添加到主隊列中:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSInvocationOperation *task1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(mainQueueTaks1) object:nil];
NSInvocationOperation *task2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(mainQueueTaks2) object:nil];
NSBlockOperation *task3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueTask3 - %@", [NSThread currentThread]);
}];
NSInvocationOperation *task4 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(mainQueueTaks4) object:nil];
[mainQueue addOperation:task1];
[mainQueue addOperation:task2];
[mainQueue addOperation:task3];
[mainQueue addOperation:task4];
- (void)mainQueueTaks1
{
NSLog(@"mainQueueTask1 - %@", [NSThread currentThread]);
}
- (void)mainQueueTaks2
{
NSLog(@"mainQueueTask2 - %@", [NSThread currentThread]);
}
- (void)mainQueueTaks4
{
NSLog(@"mainQueueTask4 - %@", [NSThread currentThread]);
}
打印結(jié)果:
2019-06-23 15:29:32.737797+0800 Thread-Test[15450:349967] mainQueueTask1 - <NSThread: 0x600003e75440>{number = 1, name = main}
2019-06-23 15:29:32.738290+0800 Thread-Test[15450:349967] mainQueueTask2 - <NSThread: 0x600003e75440>{number = 1, name = main}
2019-06-23 15:29:32.738491+0800 Thread-Test[15450:349967] mainQueueTask3 - <NSThread: 0x600003e75440>{number = 1, name = main}
2019-06-23 15:29:32.738714+0800 Thread-Test[15450:349967] mainQueueTask4 - <NSThread: 0x600003e75440>{number = 1, name = main}
可以看出四個任務(wù)不管是用NSInvocationOperation還是NSBlockOperation創(chuàng)建,同時添加到主隊列時,是串行執(zhí)行每個任務(wù)的。
這里有一種情況使用時要留心,就是NSBlockOperation的addExecutionBlock使用時,很容易產(chǎn)生幻覺造成錯誤的結(jié)果。
舉個例子:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSBlockOperation *blockTask1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueBlockTask1 - %@", [NSThread currentThread]);
}];
[blockTask1 addExecutionBlock:^{
NSLog(@"mainQueueBlockTask1v1 - %@", [NSThread currentThread]);
}];
NSBlockOperation *blockTask2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueBlockTask2 - %@", [NSThread currentThread]);
}];
NSBlockOperation *blockTask3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueBlockTask3 - %@", [NSThread currentThread]);
}];
[mainQueue addOperation:blockTask1];
[mainQueue addOperation:blockTask2];
[mainQueue addOperation:blockTask3];
在task1上又通過addExecutionBlock添加一個任務(wù),而此時mainQueueBlockTask1v1的任務(wù)行并不會在主線程中執(zhí)行,而是自己新開了一個線程。
2019-06-23 15:33:45.917731+0800 Thread-Test[15491:351783] mainQueueBlockTask1 - <NSThread: 0x6000033ee900>{number = 1, name = main}
2019-06-23 15:33:45.917735+0800 Thread-Test[15491:351829] mainQueueBlockTask1v1 - <NSThread: 0x60000339ca80>{number = 3, name = (null)}
2019-06-23 15:33:45.918266+0800 Thread-Test[15491:351783] mainQueueBlockTask2 - <NSThread: 0x6000033ee900>{number = 1, name = main}
2019-06-23 15:33:45.918482+0800 Thread-Test[15491:351783] mainQueueBlockTask3 - <NSThread: 0x6000033ee900>{number = 1, name = main}
- 其他隊列創(chuàng)建方法
其他通過init方法創(chuàng)建的隊列都是自定義隊列:
NSOperationQueue *otherQueue = [[NSOperationQueue alloc] init];
任務(wù)添加到隊列的方法與主隊列的操作一樣。
- 更多方法添加隊列
NSOperationQueue還有兩個添加任務(wù)的方法,一個是一次性添加多個任務(wù)的方法,一個是直接通過Block添加任務(wù)。
// 一次性添加所有任務(wù)到隊列中
[otherQueue addOperations:@[task1, task2] waitUntilFinished:YES];
// 通過block直接創(chuàng)建任務(wù)添加到隊列中
[otherQueue addOperationWithBlock:^{
}];
5、NSOperationQueue的串行和并行實現(xiàn)
上面講到其他隊列的創(chuàng)建方法時,與主隊列有一點不一樣的是,如何實現(xiàn)其他隊列的串行和并行呢?實際上在自定義NSOperation中已經(jīng)實現(xiàn)過并發(fā)和非并發(fā),但是有沒有更快速的方法呢?答案是有。
NSOperationQueue提供了一個比較重要的屬性maxConcurrentOperationCount,即最大的并發(fā)操作數(shù),說明清楚這里是在一個隊列中最多能同時執(zhí)行的任務(wù)數(shù),并不是只能開一個線程這個說法。那么對于這個屬性有如下幾種設(shè)置:
默認(rèn)情況maxConcurrentOperationCount 為-1,表示不進行限制,可進行并發(fā)執(zhí)行。
設(shè)置maxConcurrentOperationCount 為1時,隊列為串行隊列,只能串行執(zhí)行。
設(shè)置maxConcurrentOperationCount 大于1時,隊列為并發(fā)隊列。
6、NSOperationQueue添加依賴
文章一開始就講到了NSOperationQueue可以直接給不同的任務(wù)間添加依賴,從而實現(xiàn)很多常見的需求。
NSOperation中針對依賴關(guān)系,提供了三個接口:
(void)addDependency:(NSOperation *)op,這個方法用來添加依賴,當(dāng)前的操作要依賴操作op,即op完成了才執(zhí)行當(dāng)前操作。
(void)removeDependency:(NSOperation *)op,這個方法是移除依賴,當(dāng)前操作不再依賴操作op。
@property (readonly, copy) NSArray<NSOperation *> *dependencies,這個屬性表示在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對象數(shù)組。
來實現(xiàn)幾個任務(wù):
NSOperationQueue *otherQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1 - %@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2 - %@", [NSThread currentThread]);
}];
NSBlockOperation *task3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task3 - %@", [NSThread currentThread]);
}];
NSBlockOperation *task4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task4 - %@", [NSThread currentThread]);
}];
// 設(shè)置最大并發(fā)數(shù)
otherQueue.maxConcurrentOperationCount = 1;
// 任務(wù)1依賴任務(wù)2
[task1 addDependency:task2];
[otherQueue addOperation:task1];
[otherQueue addOperation:task2];
[otherQueue addOperation:task3];
[otherQueue addOperation:task4];
這里設(shè)置了最大并發(fā)數(shù)為1,表示四個任務(wù)將會串聯(lián)執(zhí)行,又設(shè)置了task1依賴于task2,所以task1會在task2后面執(zhí)行。
2019-06-23 16:01:33.737829+0800 Thread-Test[15745:363410] task2 - <NSThread: 0x6000002daac0>{number = 3, name = (null)}
2019-06-23 16:01:33.738614+0800 Thread-Test[15745:363409] task1 - <NSThread: 0x6000002daa40>{number = 4, name = (null)}
2019-06-23 16:01:33.740211+0800 Thread-Test[15745:363410] task3 - <NSThread: 0x6000002daac0>{number = 3, name = (null)}
2019-06-23 16:01:33.741006+0800 Thread-Test[15745:363409] task4 - <NSThread: 0x6000002daa40>{number = 4, name = (null)}
注意,添加依賴的代碼要放在任務(wù)添加到隊列前面執(zhí)行。
7、NSOperation的優(yōu)先級設(shè)置
NSOperation中提供了一個設(shè)置任務(wù)優(yōu)先級的屬性queuePriority,從而可以調(diào)整任務(wù)執(zhí)行的順序。個人覺得這個屬性名有點誤導(dǎo)人,字面意思是隊列優(yōu)先級,實際反映的是添加到這個隊列上的任務(wù)的優(yōu)先級。
來看一下蘋果官方文檔對這個屬性的說明:

從這段說明中可以得到如下信息
- 設(shè)置優(yōu)先級的對象是任務(wù)(operations)。
- 任務(wù)執(zhí)行的一般順序是代碼讀取的順序,當(dāng)然這里并不是一定的,除非是設(shè)置了依賴關(guān)系。
- 依賴的優(yōu)先級比設(shè)置優(yōu)先級的順序更靠前,即任務(wù)的執(zhí)行順序先看依賴關(guān)系,再看優(yōu)先級
- 任務(wù)的默認(rèn)優(yōu)先級是normal級別的
NSOperation的優(yōu)先級級別主要有下面幾種:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L, // 很低級別
NSOperationQueuePriorityLow = -4L, // 較低級別
NSOperationQueuePriorityNormal = 0, // 默認(rèn)級別
NSOperationQueuePriorityHigh = 4, // 較高級別
NSOperationQueuePriorityVeryHigh = 8 // 很高級別
};
官方文檔也強調(diào)了,任務(wù)的優(yōu)先級是針對當(dāng)前同一個隊列而言的,如果是在兩個不同的隊列之間,則有可能優(yōu)先級低的要比優(yōu)先級高的優(yōu)先執(zhí)行。
另外,即使是在同一個隊列中,不同任務(wù)設(shè)置了優(yōu)先級時,也還要看這多個任務(wù)是否都處在就緒的狀態(tài)(ready for execute),所以這個優(yōu)先級又是在任務(wù)都處在了就緒狀態(tài)的基礎(chǔ)上的。
參考如下這篇文章:
iOS 多線程:『NSOperation、NSOperationQueue』詳盡總結(jié)
當(dāng)一個操作的所有依賴都已經(jīng)完成時,操作對象通常會進入準(zhǔn)備就緒狀態(tài),等待執(zhí)行。如果任務(wù)沒有添加過依賴,那么當(dāng)他添加到隊列中那一刻就是出于就緒狀態(tài)了。
所以,綜上所述,任務(wù)間的優(yōu)先級遵循如下規(guī)則:
- 優(yōu)先級是針對同一個隊列中的任務(wù)
- 優(yōu)先級是在就緒狀態(tài)任務(wù)的基礎(chǔ)上進行比較
- 優(yōu)先級的優(yōu)先比依賴關(guān)系的優(yōu)先級低,即先比較依賴關(guān)系
來看一下優(yōu)先級設(shè)置的代碼,還是上面那段代碼,但是把task4的優(yōu)先級提高:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSBlockOperation *blockTask1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueBlockTask1 - %@", [NSThread currentThread]);
}];
// [blockTask1 addExecutionBlock:^{
// NSLog(@"mainQueueBlockTask1v1 - %@", [NSThread currentThread]);
// }];
NSBlockOperation *blockTask2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueBlockTask2 - %@", [NSThread currentThread]);
}];
NSBlockOperation *blockTask3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueBlockTask3 - %@", [NSThread currentThread]);
}];
NSBlockOperation *blockTask4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"mainQueueBlockTask4 - %@", [NSThread currentThread]);
}];
[blockTask4 setQueuePriority:NSOperationQueuePriorityVeryHigh];
[mainQueue addOperation:blockTask1];
[mainQueue addOperation:blockTask2];
[mainQueue addOperation:blockTask3];
[mainQueue addOperation:blockTask4];
但是打印結(jié)果有點出人意料,目前還沒有想明白,留個疑問吧。按照正常理解,這里沒有設(shè)置任何依賴關(guān)系,提高了task4的優(yōu)先級,應(yīng)該task4最先執(zhí)行才對,但是不管是提高task4,還是降低task1的優(yōu)先級,這個task1都在task4前面執(zhí)行。。。
打印結(jié)果:
2019-06-23 21:15:54.390891+0800 Thread-Test[16903:411016] mainQueueBlockTask1 - <NSThread: 0x600003ce8040>{number = 1, name = main}
2019-06-23 21:15:54.391317+0800 Thread-Test[16903:411016] mainQueueBlockTask4 - <NSThread: 0x600003ce8040>{number = 1, name = main}
2019-06-23 21:15:54.391483+0800 Thread-Test[16903:411016] mainQueueBlockTask2 - <NSThread: 0x600003ce8040>{number = 1, name = main}
2019-06-23 21:15:54.391681+0800 Thread-Test[16903:411016] mainQueueBlockTask3 - <NSThread: 0x600003ce8040>{number = 1, name = main}
如上就是NSOperation和NSOperationQueue相關(guān)的內(nèi)容,關(guān)于線程安全我覺得需要用一個篇幅來詳細(xì)講解,這里先不做說明。