iOS多線程:NSOperation、NSOperationQueue總結(jié)

NSOperation、NSOperationQueue 是蘋(píng)果提供給我們的一套多線程解決方案。NSOperation、NSOperationQueue是基于GCD更高一層的封裝,完全面向?qū)ο蟆5潜菺CD更簡(jiǎn)單易用、代碼可讀性也更高。

一簡(jiǎn)介

NSOperation本身是抽象基類(lèi),不能直接使用,但是他封裝了需要執(zhí)行的操作和執(zhí)行操作所需的數(shù)據(jù)方法等。在NSOperation基礎(chǔ)上,系統(tǒng)提供了兩個(gè)子類(lèi)NSBlockOperation和NSInvocationOperation供我們具體使用,當(dāng)然,我們也可以自己封裝自定義的NSOperation

使用NSOperation、NSOperationQueue優(yōu)勢(shì):

  • 可添加完成的代碼塊,在操作完成后執(zhí)行;
  • 添加操作之間的依賴(lài)關(guān)系,方便的控制執(zhí)行順序;
  • 設(shè)定操作執(zhí)行的優(yōu)先級(jí);
  • 可以很方便的取消一個(gè)操作的執(zhí)行;
  • 使用 KVO 觀察對(duì)操作執(zhí)行狀態(tài)的更改:isExecuteing、isFinishedisCancelled

二、操作-NSOperation和操作隊(duì)列-NSOperationQueue

在 NSOperation、NSOperationQueue中也有類(lèi)似的任務(wù)(操作)隊(duì)列(操作隊(duì)列)的概念。

操作(Operation):

  • 執(zhí)行操作的意思,換句話說(shuō)就是你在線程中執(zhí)行的那段代碼。

  • 在 GCD 中是放在 block中的。在 NSOperation 中,我們使用 NSOperation 子類(lèi) NSInvocationOperation、NSBlockOperation,或者自定義子類(lèi)來(lái)封裝操作。

操作隊(duì)列(Operation Queues):

  • 這里的隊(duì)列指操作隊(duì)列,即用來(lái)存放操作的隊(duì)列。不同于 GCD中的調(diào)度隊(duì)列 FIFO(先進(jìn)先出的原則。NSOperationQueue對(duì)于添加到隊(duì)列中的操作,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴(lài)關(guān)系),然后進(jìn)入就緒狀態(tài)的操作的開(kāi)始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對(duì)的優(yōu)先級(jí)決定(優(yōu)先級(jí)是操作對(duì)象自身的屬性)
  • 操作隊(duì)列通過(guò)設(shè)置最大并發(fā)操作數(shù)(maxConcurrentOperationCount來(lái)控制并發(fā)、串行。
  • NSOperationQueue 為我們提供了兩種不同類(lèi)型的隊(duì)列:主隊(duì)列和自定義隊(duì)列。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺(tái)執(zhí)行。

三、NSOperation的常用方法、屬性介紹

3.1 NSOperation方法介紹

// NSOperation實(shí)例初始化方法,用于創(chuàng)建一個(gè)NSOperation實(shí)例。
- (instancetype)init;

// 該方法是operation的起點(diǎn),如果需要?jiǎng)?chuàng)建并發(fā)operation必須,覆蓋start方法。同時(shí)調(diào)用start方法。
- (void)start;

// 該property,是用于標(biāo)示某個(gè)operation是否cancel。對(duì)于多線程來(lái)說(shuō)需要不斷檢測(cè)這個(gè)值。
- (BOOL)isCancelled;

// 調(diào)用cancel方法會(huì)取消一個(gè)operation,但是如果operation加入到Queue中或者operation已經(jīng)start了,則無(wú)法取消成功,調(diào)用cancel也不一定立即執(zhí)行cancel操作,需要等待時(shí)間周期。
- (void)cancel;

// 判定operation是否正在執(zhí)行。
- (BOOL)isExecuting;

// 判定operation是否完成,cancel掉某個(gè)operation,也會(huì)將該operation的該字段設(shè)置成為YES。
- (BOOL)isFinished;

// 判定該線程是否是并發(fā)線程,即調(diào)用該operation的start方法的線程是否與operation所在線程相同。
- (BOOL)isConcurrent;

// 在start方法開(kāi)始之前,需要確定operation是否ready,默認(rèn)為YES,如果該operation沒(méi)有ready,則不會(huì)start。
- (BOOL)isReady;

// 該方法用于配置operation之間的依賴(lài)關(guān)系,涉及執(zhí)行順序。如果不是手動(dòng)調(diào)用start去執(zhí)行operation,一定要在將其加入到Queue之前做好依賴(lài),因?yàn)橐坏┘尤氲絈ueue中,其也許很快會(huì)執(zhí)行,依賴(lài)關(guān)系將不會(huì)起作用。
- (void)addDependency:(NSOperation *)op;

// 相對(duì)應(yīng)add,其為移除兩個(gè)operation之間的依賴(lài)關(guān)系。
- (void)removeDependency:(NSOperation *)op;

// 獲取operation的依賴(lài)關(guān)系的數(shù)組。
- (NSArray *)dependencies;

// //如果將operation加入到Queue中,設(shè)定其在Queue中的優(yōu)先級(jí),優(yōu)先級(jí)高的先執(zhí)行的概率大,但并不代表一定會(huì)先執(zhí)行,執(zhí)行順序稍后介紹。
- (NSOperationQueuePriority)queuePriority;

// setter方法。
- (void)setQueuePriority:(NSOperationQueuePriority)p;

// 在operation完成之后會(huì)調(diào)用completionBlock,你可以自定義執(zhí)行行為。
- (void (^)(void))completionBlock NS_AVAILABLE(10_6, 4_0);

// 設(shè)定是否等待operation執(zhí)行結(jié)束,如果為YES,該線程會(huì)一直等待operation執(zhí)行結(jié)束,才會(huì)執(zhí)行接下來(lái)的代碼。
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);

// 設(shè)定operation的線程優(yōu)先級(jí),涉及執(zhí)行順序稍后介紹。線程優(yōu)先級(jí)默認(rèn)為0.5,最低為0,最大為1.即使設(shè)定了線程優(yōu)先級(jí),也只能保證其在main方法范圍內(nèi)有效,operation的其他代碼仍然執(zhí)行在默認(rèn)線程。
- (double)threadPriority NS_AVAILABLE(10_6, 4_0);

3.2 NSBlockOperation方法介紹

// 創(chuàng)建NSBlockOperation對(duì)象
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

// 通過(guò)addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;

3.3 NSInvocationOperation方法介紹

// 創(chuàng)建NSInvocationOperation對(duì)象 
- (instancetype)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

四、NSOperationQueue的常用方法、屬性介紹

// 取消隊(duì)列的所有操作
- (void)cancelAllOperations;

// 設(shè)置操作的暫停和恢復(fù),YES 代表暫停隊(duì)列,NO 代表恢復(fù)隊(duì)列
- (void)setSuspended:(BOOL)b;

// 判斷隊(duì)列是否處于暫停狀態(tài)
- (BOOL)isSuspended;

// 阻塞當(dāng)前線程,直到隊(duì)列中的操作全部執(zhí)行完畢
- (void)waitUntilAllOperationsAreFinished;

// 隊(duì)列中添加一個(gè) NSBlockOperation 類(lèi)型操作對(duì)象
- (void)addOperationWithBlock:(void (^)(void))block;

// 向隊(duì)列中添加操作數(shù)組,wait 標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;

// 當(dāng)前在隊(duì)列中的操作數(shù)組(某個(gè)操作執(zhí)行結(jié)束后會(huì)自動(dòng)從這個(gè)數(shù)組清除)
- (NSArray *)operations;

// 當(dāng)前隊(duì)列中的操作數(shù)
- (NSUInteger)operationCount;

//  獲取當(dāng)前隊(duì)列,如果當(dāng)前線程不是在 NSOperationQueue 上運(yùn)行則返回 nil
+ (instancetype)currentQueue;

// 獲取主隊(duì)列
+ (instancetype)mainQueue; 

這里的暫停和取消(包括操作的取消和隊(duì)列的取消)并不代表可以將當(dāng)前的操作立即取消,而是當(dāng)當(dāng)前的操作執(zhí)行完畢之后不再執(zhí)行新的操作。
暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復(fù)操作,繼續(xù)向下執(zhí)行;而取消操作之后,所有的操作就清空了,無(wú)法再接著執(zhí)行剩下的操作。

五、NSOperation 和 NSOperationQueue 基本使用

5.1 創(chuàng)建操作

NSOperation是個(gè)抽象類(lèi),不能用來(lái)封裝操作。我們只有使用它的子類(lèi)來(lái)封裝操作。我們有三種方式來(lái)封裝操作。

  • 使用子類(lèi) NSInvocationOperation
  • 使用子類(lèi) NSBlockOperation
  • 自定義繼承自 NSOperation 的子類(lèi),通過(guò)實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來(lái)封裝操作

5.1.1 使用子類(lèi) NSInvocationOperation

// 使用子類(lèi) NSInvocationOperation
- (void)invocationOperation {

    // 1.創(chuàng)建 NSInvocationOperation 對(duì)象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationTask) object:nil];

    // 2.調(diào)用 start 方法開(kāi)始執(zhí)行操作
    [op start];
}

// 操作任務(wù)
- (void)operationTask {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
        NSLog(@"thread --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
    }
}

打印日志

2019-11-22 10:44:48.218825+0800 多線程-demo[65383:21238597] thread --- <NSThread: 0x600002ac0bc0>{number = 1, name = main}
2019-11-22 10:44:49.220306+0800 多線程-demo[65383:21238597] thread --- <NSThread: 0x600002ac0bc0>{number = 1, name = main}

在沒(méi)有使用 NSOperationQueue、在主線程中單獨(dú)使用使用子類(lèi) NSInvocationOperation 執(zhí)行一個(gè)操作的情況下,操作是在當(dāng)前線程執(zhí)行的,并沒(méi)有開(kāi)啟新線程。

5.1.2 使用子類(lèi) NSBlockOperation

// 使用子類(lèi) NSBlockOperation
- (void)blockOperation {

    // 1.創(chuàng)建 NSBlockOperation 對(duì)象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];

    // 2.調(diào)用 start 方法開(kāi)始執(zhí)行操作
    [op start];
}

打印日志

2019-11-22 10:48:24.270508+0800 多線程-demo[65399:21245204] thread --- <NSThread: 0x6000016693c0>{number = 1, name = main}
2019-11-22 10:48:25.271132+0800 多線程-demo[65399:21245204] thread --- <NSThread: 0x6000016693c0>{number = 1, name = main}

在沒(méi)有使用 NSOperationQueue、在主線程中單獨(dú)使用NSBlockOperation執(zhí)行一個(gè)操作的情況下,操作是在當(dāng)前線程執(zhí)行的,并沒(méi)有開(kāi)啟新線程

NSBlockOperation還提供了一個(gè)方法 addExecutionBlock:,通過(guò) addExecutionBlock: 就可以為 NSBlockOperation添加額外的操作。這些操作(包括 blockOperationWithBlock 中的操作)可以在不同的線程中同時(shí)(并發(fā))執(zhí)行。只有當(dāng)所有相關(guān)的操作已經(jīng)完成執(zhí)行時(shí),才視為完成。

- (void)blockOperationAddExecutionBlock {

    // 1.創(chuàng)建 NSBlockOperation 對(duì)象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"thread1 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];

    // 2.添加額外的操作
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"thread2 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [op addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"thread3 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    // 3.調(diào)用 start 方法開(kāi)始執(zhí)行操作
    [op start];
}

打印日志

2019-11-22 10:54:03.107072+0800 多線程-demo[65435:21257518] thread2 --- <NSThread: 0x6000036b06c0>{number = 5, name = (null)}
2019-11-22 10:54:03.107073+0800 多線程-demo[65435:21257522] thread3 --- <NSThread: 0x6000036dab80>{number = 6, name = (null)}
2019-11-22 10:54:03.107072+0800 多線程-demo[65435:21257472] thread1 --- <NSThread: 0x6000036b5c80>{number = 1, name = main}
2019-11-22 10:54:04.107443+0800 多線程-demo[65435:21257472] thread1 --- <NSThread: 0x6000036b5c80>{number = 1, name = main}
2019-11-22 10:54:04.107468+0800 多線程-demo[65435:21257518] thread2 --- <NSThread: 0x6000036b06c0>{number = 5, name = (null)}
2019-11-22 10:54:04.107494+0800 多線程-demo[65435:21257522] thread3 --- <NSThread: 0x6000036dab80>{number = 6, name = (null)}

使用子類(lèi) NSBlockOperation,并調(diào)用方法 AddExecutionBlock: 的情況下,blockOperationWithBlock:方法中的操作 和 addExecutionBlock: 中的操作是在不同的線程中異步執(zhí)行的。而且,這次執(zhí)行結(jié)果中 blockOperationWithBlock:方法中的操作也不是在當(dāng)前線程(主線程)中執(zhí)行的。從而印證了blockOperationWithBlock: 中的操作也可能會(huì)在其他線程(非當(dāng)前線程)中執(zhí)行。

一般情況下,如果一個(gè) NSBlockOperation 對(duì)象封裝了多個(gè)操作。NSBlockOperation是否開(kāi)啟新線程,取決于操作的個(gè)數(shù)。如果添加的操作的個(gè)數(shù)多,就會(huì)自動(dòng)開(kāi)啟新線程。當(dāng)然開(kāi)啟的線程數(shù)是由系統(tǒng)來(lái)決定的。

5.1.3 使用自定義繼承自 NSOperation 的子類(lèi)

如果使用子類(lèi) NSInvocationOperation、NSBlockOperation不能滿足日常需求,我們可以使用自定義繼承自NSOperation 的子類(lèi)??梢酝ㄟ^(guò)重寫(xiě) main 或者 start 方法 來(lái)定義自己的 NSOperation 對(duì)象。重寫(xiě)main方法比較簡(jiǎn)單,我們不需要管理操作的狀態(tài)屬性 isExecutingisFinished。當(dāng) main 執(zhí)行完返回的時(shí)候,這個(gè)操作就結(jié)束了。

// LMOperation.h 文件
@interface LMOperation : NSOperation

@end

// LMOperation.m 文件
@implementation LMOperation

- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            NSLog(@"thread --- %@", [NSThread currentThread]);
        }
    }
}

@end

// 導(dǎo)入頭文件 LMOperation.h
- (void)customOperation {

    // 1.創(chuàng)建 YSCOperation 對(duì)象
    LMOperation *op = [[LMOperation alloc] init];
    
    // 2.調(diào)用 start 方法開(kāi)始執(zhí)行操作
    [op start];
}

打印日志

2019-11-22 11:01:15.707199+0800 多線程-demo[65475:21275462] thread --- <NSThread: 0x600003e3a100>{number = 1, name = main}
2019-11-22 11:01:15.707402+0800 多線程-demo[65475:21275462] thread --- <NSThread: 0x600003e3a100>{number = 1, name = main}

在沒(méi)有使用 NSOperationQueue、在主線程單獨(dú)使用自定義繼承自 NSOperation 的子類(lèi)的情況下,是在主線程執(zhí)行操作,并沒(méi)有開(kāi)啟新線程。

5.2 創(chuàng)建隊(duì)列

NSOperationQueue一共有兩種隊(duì)列:主隊(duì)列、自定義隊(duì)列。其中自定義隊(duì)列同時(shí)包含了串行、并發(fā)功能。下邊是主隊(duì)列、自定義隊(duì)列的基本創(chuàng)建方法和特點(diǎn)。

主隊(duì)列

凡是添加到主隊(duì)列中的操作,都會(huì)放到主線程中執(zhí)行,但不包括操作使用addExecutionBlock:添加的額外操作,額外操作可能在其他線程執(zhí)行

// 主隊(duì)列獲取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

自定義隊(duì)列

添加到這種隊(duì)列中的操作,就會(huì)自動(dòng)放到子線程中執(zhí)行, 同時(shí)包含了:串行、并發(fā)功能。

// 自定義隊(duì)列創(chuàng)建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

5.3 將操作加入到隊(duì)列中

5.3.1 addOperation添加操作到隊(duì)列

// 使用 addOperation: 將操作加入到操作隊(duì)列中
- (void)addOperationToQueue {

    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.創(chuàng)建操作
    // 使用 NSInvocationOperation 創(chuàng)建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationTask) object:nil];

    // 使用 NSBlockOperation 創(chuàng)建操作3
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread2 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [op2 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread3 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];

    // 3.使用 addOperation: 添加所有操作到隊(duì)列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
}

// 操作任務(wù)
- (void)operationTask {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
        NSLog(@"thread1 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
    }
}

打印日志

2019-11-22 11:10:16.160387+0800 多線程-demo[65529:21296746] thread2 --- <NSThread: 0x600001eac940>{number = 3, name = (null)}
2019-11-22 11:10:16.160395+0800 多線程-demo[65529:21296740] thread1 --- <NSThread: 0x600001ec4300>{number = 5, name = (null)}
2019-11-22 11:10:16.160402+0800 多線程-demo[65529:21296743] thread3 --- <NSThread: 0x600001ec6c80>{number = 7, name = (null)}
2019-11-22 11:10:17.160850+0800 多線程-demo[65529:21296746] thread2 --- <NSThread: 0x600001eac940>{number = 3, name = (null)}
2019-11-22 11:10:17.160934+0800 多線程-demo[65529:21296740] thread1 --- <NSThread: 0x600001ec4300>{number = 5, name = (null)}
2019-11-22 11:10:17.160934+0800 多線程-demo[65529:21296743] thread3 --- <NSThread: 0x600001ec6c80>{number = 7, name = (null)}

使用NSOperation子類(lèi)創(chuàng)建操作,并使用 addOperation: 將操作加入到操作隊(duì)列后能夠開(kāi)啟新線程,進(jìn)行并發(fā)執(zhí)行。

5.3.2 addOperationWithBlock添加操作到隊(duì)列

// 使用 addOperationWithBlock: 將操作加入到操作隊(duì)列中
- (void)addOperationWithBlockToQueue {
    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.使用 addOperationWithBlock: 添加操作到隊(duì)列中
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"thread1 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"thread2 ---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
}

打印日志

2019-11-22 11:14:29.227160+0800 多線程-demo[65543:21306313] thread1 --- <NSThread: 0x6000001ee080>{number = 6, name = (null)}
2019-11-22 11:14:29.227163+0800 多線程-demo[65543:21306314] thread2 ---<NSThread: 0x6000001b8800>{number = 5, name = (null)}
2019-11-22 11:14:31.231445+0800 多線程-demo[65543:21306314] thread2 ---<NSThread: 0x6000001b8800>{number = 5, name = (null)}
2019-11-22 11:14:31.231449+0800 多線程-demo[65543:21306313] thread1 --- <NSThread: 0x6000001ee080>{number = 6, name = (null)}

使用 addOperationWithBlock: 將操作加入到操作隊(duì)列后能夠開(kāi)啟新線程,進(jìn)行并發(fā)執(zhí)行。

六、NSOperationQueue 控制串行執(zhí)行、并發(fā)執(zhí)行

NSOperationQueue通過(guò)maxConcurrentOperationCount(最大并發(fā)操作數(shù))。用來(lái)控制一個(gè)特定隊(duì)列中可以有多少個(gè)操作同時(shí)參與并發(fā)執(zhí)行。

最大并發(fā)操作數(shù):maxConcurrentOperationCount

  • maxConcurrentOperationCount 默認(rèn)情況下為-1,表示不進(jìn)行限制,可進(jìn)行并發(fā)執(zhí)行

  • maxConcurrentOperationCount 為1時(shí),隊(duì)列為串行隊(duì)列。只能串行執(zhí)行

  • maxConcurrentOperationCount 大于1時(shí),隊(duì)列為并發(fā)隊(duì)列。操作并發(fā)執(zhí)行,當(dāng)然這個(gè)值不應(yīng)超過(guò)系統(tǒng)限制,即使自己設(shè)置一個(gè)很大的值,系統(tǒng)也會(huì)自動(dòng)調(diào)整為 min{自己設(shè)定的值,系統(tǒng)設(shè)定的默認(rèn)最大值}。

// 設(shè)置 MaxConcurrentOperationCount(最大并發(fā)操作數(shù))
- (void)setMaxConcurrentOperationCount {

    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.設(shè)置最大并發(fā)操作數(shù)
    queue.maxConcurrentOperationCount = 1; // 串行隊(duì)列
// queue.maxConcurrentOperationCount = 2; // 并發(fā)隊(duì)列
// queue.maxConcurrentOperationCount = 6; // 并發(fā)隊(duì)列

    // 3.添加操作
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread1 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread2 ---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
}

最大并發(fā)操作數(shù)為1,為串行隊(duì)列,打印日志:

2019-11-22 11:22:59.988321+0800 多線程-demo[65566:21319793] thread1 --- <NSThread: 0x60000388d6c0>{number = 6, name = (null)}
2019-11-22 11:23:00.992952+0800 多線程-demo[65566:21319793] thread1 --- <NSThread: 0x60000388d6c0>{number = 6, name = (null)}
2019-11-22 11:23:01.997176+0800 多線程-demo[65566:21319797] thread2 ---<NSThread: 0x6000038b4a00>{number = 3, name = (null)}
2019-11-22 11:23:03.001493+0800 多線程-demo[65566:21319797] thread2 ---<NSThread: 0x6000038b4a00>{number = 3, name = (null)}

最大并發(fā)操作數(shù)為2,為并發(fā)隊(duì)列,打印日志:

2019-11-22 11:24:01.876410+0800 多線程-demo[65579:21321486] thread2 ---<NSThread: 0x6000031543c0>{number = 5, name = (null)}
2019-11-22 11:24:01.876436+0800 多線程-demo[65579:21321488] thread1 --- <NSThread: 0x60000315e540>{number = 6, name = (null)}
2019-11-22 11:24:02.876903+0800 多線程-demo[65579:21321488] thread1 --- <NSThread: 0x60000315e540>{number = 6, name = (null)}
2019-11-22 11:24:02.876903+0800 多線程-demo[65579:21321486] thread2 ---<NSThread: 0x6000031543c0>{number = 5, name = (null)}

最大并發(fā)操作數(shù)為6,為并發(fā)隊(duì)列,打印日志:

2019-11-22 11:24:48.046063+0800 多線程-demo[65581:21322720] thread1 --- <NSThread: 0x600003248b80>{number = 5, name = (null)}
2019-11-22 11:24:48.046064+0800 多線程-demo[65581:21322718] thread2 ---<NSThread: 0x600003249c80>{number = 7, name = (null)}
2019-11-22 11:24:49.047850+0800 多線程-demo[65581:21322718] thread2 ---<NSThread: 0x600003249c80>{number = 7, name = (null)}
2019-11-22 11:24:49.047867+0800 多線程-demo[65581:21322720] thread1 --- <NSThread: 0x600003248b80>{number = 5, name = (null)}

當(dāng)最大并發(fā)操作數(shù)為1時(shí),操作是按順序串行執(zhí)行的,并且一個(gè)操作完成之后,下一個(gè)操作才開(kāi)始執(zhí)行。當(dāng)最大操作并發(fā)數(shù)為2時(shí),操作是并發(fā)執(zhí)行的,可以同時(shí)執(zhí)行兩個(gè)操作。而開(kāi)啟線程數(shù)量是由系統(tǒng)決定的,不需要我們來(lái)管理。

七、NSOperation 操作依賴(lài)

NSOperation、NSOperationQueue最吸引人的地方是它能添加操作之間的依賴(lài)關(guān)系。通過(guò)操作依賴(lài),我們可以很方便的控制操作之間的執(zhí)行先后順序。

  • - (void)addDependency:(NSOperation *)op; 添加依賴(lài),使當(dāng)前操作依賴(lài)于操作 op 的完成。

  • - (void)removeDependency:(NSOperation *)op; 移除依賴(lài),取消當(dāng)前操作對(duì)操作 op 的依賴(lài)。

  • @property (readonly, copy) NSArray *dependencies; 在當(dāng)前操作開(kāi)始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組。

// 操作依賴(lài)
- (void)addDependency {

    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.創(chuàng)建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread1 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread2 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];

    // 3.添加依賴(lài)
    [op2 addDependency:op1]; // 讓op2 依賴(lài)于 op1,則先執(zhí)行op1,在執(zhí)行op2

    // 4.添加操作到隊(duì)列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

打印日志

2019-11-22 11:27:48.796309+0800 多線程-demo[65595:21327089] thread1 --- <NSThread: 0x6000005929c0>{number = 6, name = (null)}
2019-11-22 11:27:49.797401+0800 多線程-demo[65595:21327089] thread1 --- <NSThread: 0x6000005929c0>{number = 6, name = (null)}
2019-11-22 11:27:50.802102+0800 多線程-demo[65595:21327093] thread2 --- <NSThread: 0x6000005acf40>{number = 3, name = (null)}
2019-11-22 11:27:51.805816+0800 多線程-demo[65595:21327093] thread2 --- <NSThread: 0x6000005acf40>{number = 3, name = (null)}

通過(guò)添加操作依賴(lài),無(wú)論運(yùn)行幾次,其結(jié)果都是 op1 先執(zhí)行,op2 后執(zhí)行。

八、NSOperation 優(yōu)先級(jí)

NSOperation 提供了queuePriority(優(yōu)先級(jí))屬性,queuePriority屬性適用于同一操作隊(duì)列中的操作,不適用于不同操作隊(duì)列中的操作。默認(rèn)情況下,所有新創(chuàng)建的操作對(duì)象優(yōu)先級(jí)都是NSOperationQueuePriorityNormal。但是我們可以通過(guò)setQueuePriority:方法來(lái)改變當(dāng)前操作在同一隊(duì)列中的執(zhí)行優(yōu)先級(jí)。

// 優(yōu)先級(jí)的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

對(duì)于添加到隊(duì)列中的操作,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴(lài)關(guān)系),然后進(jìn)入就緒狀態(tài)的操作的開(kāi)始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對(duì)的優(yōu)先級(jí)決定(優(yōu)先級(jí)是操作對(duì)象自身的屬性)。

queuePriority 屬性的作用

  • 一個(gè)操作的所有依賴(lài)都已經(jīng)完成時(shí),操作對(duì)象通常會(huì)進(jìn)入準(zhǔn)備就緒狀態(tài),等待執(zhí)行。

  • queuePriority 屬性決定了進(jìn)入準(zhǔn)備就緒狀態(tài)下的操作之間的開(kāi)始執(zhí)行順序。并且,優(yōu)先級(jí)不能取代依賴(lài)關(guān)系。

  • 如果一個(gè)隊(duì)列中既包含高優(yōu)先級(jí)操作,又包含低優(yōu)先級(jí)操作,并且兩個(gè)操作都已經(jīng)準(zhǔn)備就緒,那么隊(duì)列先執(zhí)行高優(yōu)先級(jí)操作。

  • 如果,一個(gè)隊(duì)列中既包含了準(zhǔn)備就緒狀態(tài)的操作,又包含了未準(zhǔn)備就緒的操作,未準(zhǔn)備就緒的操作優(yōu)先級(jí)比準(zhǔn)備就緒的操作優(yōu)先級(jí)高。那么,雖然準(zhǔn)備就緒的操作優(yōu)先級(jí)低,也會(huì)優(yōu)先執(zhí)行。優(yōu)先級(jí)不能取代依賴(lài)關(guān)系。如果要控制操作間的啟動(dòng)順序,則必須使用依賴(lài)關(guān)系。

九、NSOperation、NSOperationQueue 線程間的通信

// 線程間通信
- (void)communication {

    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 異步進(jìn)行耗時(shí)操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
            NSLog(@"thread1 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
        }

        // 回到主線程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 進(jìn)行一些 UI 刷新等操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
                NSLog(@"thread2 --- %@", [NSThread currentThread]); // 打印當(dāng)前線程
            }
        }];
    }];
}

打印日志

2019-11-22 11:37:11.257207+0800 多線程-demo[65626:21341680] thread1 --- <NSThread: 0x600003fa9c40>{number = 6, name = (null)}
2019-11-22 11:37:12.262269+0800 多線程-demo[65626:21341680] thread1 --- <NSThread: 0x600003fa9c40>{number = 6, name = (null)}
2019-11-22 11:37:13.263827+0800 多線程-demo[65626:21341609] thread2 --- <NSThread: 0x600003fc2180>{number = 1, name = main}
2019-11-22 11:37:14.265262+0800 多線程-demo[65626:21341609] thread2 --- <NSThread: 0x600003fc2180>{number = 1, name = main}

通過(guò)線程間的通信,先在其他線程中執(zhí)行操作,等操作執(zhí)行完了之后再回到主線程執(zhí)行主線程的相應(yīng)操作。


本文首發(fā)于我的個(gè)人博客 https://limeng99.club/,轉(zhuǎn)載請(qǐng)標(biāo)明出處。

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

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

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