iOS多線程(四):多線程實現(xiàn)方案(NSOperation和NSOperationQueue)

這一篇文章主要介紹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與NSOperation比較
  • 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ā)的,相對步驟要多一點。

NSOperation蘋果官方文檔

先來看一下非并發(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)先級。

來看一下蘋果官方文檔對這個屬性的說明:


NSOperation優(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ì)講解,這里先不做說明。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 趕巧看完電影,看朋友圈里說今天是國際不打小孩日~ 電影結(jié)束之后,自己還在很灰色的情緒里。雖然眼淚流出來了,并不真的...
    芯滿亦足閱讀 545評論 0 1
  • 在《天天向上》的節(jié)目中有一個人,因陪伴了觀眾很多年,在節(jié)目中為觀眾送上各種經(jīng)典的段子等種種行為,被觀眾所喜愛,也擁...
    慢娛時光閱讀 386評論 0 0
  • 方云含淚看向楚曉生和雷陽背后的陳元北,點了點頭,“謝謝!” 諸葛梵看見楚曉生后停了手,方云戒備著回到了方鳳芝身邊。...
    竹曳的雷厲風(fēng)行閱讀 267評論 0 1
  • 其實老二最慘了,這些都是老爺子安排的。 弟兄四人,就老二是最聽老爺子話的人,而且老二家里的也特別爭氣,接連給生了兩...
    聶聶風(fēng)閱讀 629評論 3 4
  • 該系列文章源自于公司內(nèi)部分享;希望能給大家?guī)砑夹g(shù)上的提升。文章其實早就在準(zhǔn)備中了,只是一直沒有加以整理輸出;恰巧...
    qiujuer閱讀 645評論 2 10

友情鏈接更多精彩內(nèi)容