iOS詳解多線程(實現(xiàn)篇——NSOperation)

NSOperation.png

上一節(jié)中,我們探究了GCD實現(xiàn)多線程的各種方式,有圖有真相,不清楚的朋友們可以回去看一看啦。這一節(jié)中,我們來看看蘋果官方給我們提供的又一個實現(xiàn)多線程的方式,NSOperation。

GCD鏈接:iOS詳解多線程(實現(xiàn)篇——GCD)
NSThread鏈接:詳解多線程(實現(xiàn)篇——NSThread)
多線程概念篇鏈接:詳解多線程(概念篇——進程、線程以及多線程原理)

源碼鏈接:https://github.com/weiman152/Multithreading.git

多線程的實現(xiàn)方法

1.NSThread(OC)

2.GCD(C語言)

3.NSOperation(OC)

4.C語言的pthread(C語言)
5.其他實現(xiàn)多線程方法

本節(jié)主要內(nèi)容

  1. NSOperation是什么
  2. NSOperation的使用
    2_1.NSOperation對象的創(chuàng)建(三種方式)
    2_2. NSOperationQueue的使用
    2_3. 任務(wù)和隊列結(jié)合使用
  3. NSOperation其他重要用法
    3_1.NSOperation的依賴
  4. 案例
1.NSOperation是什么

NSOperation是蘋果官方提供的面向?qū)ο蟮囊环N解決多線程的方式。NSOperation是對GCD的封裝。我們已經(jīng)知道,GCD是C語言風(fēng)格的,NSOperation是OC的面向?qū)ο蟮模菺CD多了一些更加簡單實用的功能,使用起來更加的便捷,容易理解。
NSOperation 和NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列。

2.NSOperation的使用

我們現(xiàn)在知道,NSOperation是對GCD的封裝,復(fù)習(xí)一下GCD的使用步驟:
1.創(chuàng)建隊列(串行、并發(fā));
2.創(chuàng)建任務(wù)(同步、異步);
把任務(wù)放在隊列中執(zhí)行。

NSOperation的使用也是差不多的步驟:
1.將任務(wù)封裝到NSOperation對象中;
2.將NSOperation對象添加到NSOperationQueue中;
系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來,并將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行。

2.1 NSOperation對象的創(chuàng)建
注意:NSOperation是一個抽象類,不具備封裝操作的能力,必須使用它的子類。

NSOperation有三個子類都可以創(chuàng)建任務(wù)對象。
1》NSInvocationOperation

先創(chuàng)建一個待執(zhí)行的函數(shù),run

-(void)runWithName:(NSString *)name{
    NSLog(@"-------%@-------",name);
    NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    NSLog(@"哈哈哈哈,我是個任務(wù),要執(zhí)行2秒哦");
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"任務(wù)結(jié)束啦");
}

NSInvocationOperation執(zhí)行run方法。

- (IBAction)test1:(id)sender {
    //1.NSInvocationOperation
    NSInvocationOperation * invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runWithName:) object:@"NSInvocationOperation"];
    //啟動
    [invocationOp start];
}

結(jié)果:


image.png

2》NSBlockOperation
NSBlockOperation是最常用的一種方式了,可以追加任務(wù),并且追加的任務(wù)是在子線程中執(zhí)行的。

- (IBAction)test2:(id)sender {
    //2.NSBlockOperation(最常使用)
    NSBlockOperation * blockOp = [NSBlockOperation blockOperationWithBlock:^{
        //要執(zhí)行的操作,目前是主線程
        NSLog(@"NSBlockOperation 創(chuàng)建,線程:%@",[NSThread currentThread]);
    }];
    //2.1 追加任務(wù),在子線程中執(zhí)行
    [blockOp addExecutionBlock:^{
        NSLog(@"追加任務(wù)一");
        [self runWithName:@"NSBlockOperation 追加"];
    }];
    [blockOp addExecutionBlock:^{
        NSLog(@"追加任務(wù)二, %@",[NSThread currentThread]);
    }];
    [blockOp start];
}

結(jié)果:


image.png

3》自定義類繼承自NSOperation,實現(xiàn)main方法。
我們創(chuàng)建一個類WMOperation繼承自NSOperation,如下圖:


image.png

實現(xiàn)main方法:


image.png

使用:

- (IBAction)test3:(id)sender {
    WMOperation * wmOp = [[WMOperation alloc] init];
    [wmOp start];
}

結(jié)果:


image.png

上面是最簡單的自定義Operation,是一種串行操作,也是在主線程進行的操作。為了更好的探究自定義類,我們再新建兩個類,一個是復(fù)雜一點的串行,一個是并行操作。
新建兩個類,如下圖:


image.png

WMCXOperation:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
/**
 自定義串行操作,NSOperation的子類
 */
@interface WMCXOperation : NSOperation

@end

NS_ASSUME_NONNULL_END

#import "WMCXOperation.h"

@implementation WMCXOperation

- (void)main {
    NSLog(@"main 開始啦");
    @try {
        //任務(wù)是否結(jié)束標(biāo)識
        BOOL isFinish = NO;
        //只有當(dāng)沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
        while (isFinish==NO&&(self.isCancelled==NO)) {
            //睡眠1秒,模擬耗時
            sleep(1);
            NSLog(@"線程:%@",[NSThread currentThread]);
            isFinish = YES;
        }
    } @catch (NSException *exception) {
        NSLog(@"出現(xiàn)異常:%@",exception);
    } @finally {
        NSLog(@"哈哈哈");
    }
    NSLog(@"main 結(jié)束啦");
}

@end

測試:

- (IBAction)test3:(id)sender {
    WMOperation * wmOp = [[WMOperation alloc] init];
    [wmOp start];
    
    //自定義串行
    WMCXOperation * cxOp = [[WMCXOperation alloc] init];
    NSLog(@"任務(wù)開始");
    [cxOp start];
    NSLog(@"任務(wù)結(jié)束");
    
    //自定義并行
}

結(jié)果:


image.png

從結(jié)果可以看出,任務(wù)的執(zhí)行依然是在主線程,是串行操作。

自定義并行操作:
WMBXOperation

//
//  WMBXOperation.h
//  Multithreading
//
//  Created by wenhuanhuan on 2020/10/14.
//  Copyright ? 2020 weiman. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
/**
自定義并行操作,NSOperation的子類
 
 自定義并行的 NSOperation 則要復(fù)雜一點,首先必須重寫以下幾個方法:

 start: 所有并行的 Operations 都必須重寫這個方法,然后在你想要執(zhí)行的線程中手動調(diào)用這個方法。注意:任何時候都不能調(diào)用父類的start方法。
 main: 在start方法中調(diào)用,但是注意要定義獨立的自動釋放池與別的線程區(qū)分開。
 isExecuting: 是否執(zhí)行中,需要實現(xiàn)KVO通知機制。
 isFinished: 是否已完成,需要實現(xiàn)KVO通知機制。
 isConcurrent: 該方法現(xiàn)在已經(jīng)由isAsynchronous方法代替,并且 NSOperationQueue 也已經(jīng)忽略這個方法的值。
 isAsynchronous: 該方法默認(rèn)返回 NO ,表示非并發(fā)執(zhí)行。并發(fā)執(zhí)行需要自定義并且返回 YES。后面會根據(jù)這個返回值來決定是否并發(fā)。
 與非并發(fā)操作不同的是,需要另外自定義一個方法來執(zhí)行操作而不是直接調(diào)用start方法.
*/
@interface WMBXOperation : NSOperation

//自定義方法,啟動操作
-(BOOL)wmStart:(NSOperation *)op;

@end

NS_ASSUME_NONNULL_END

//
//  WMBXOperation.m
//  Multithreading
//
//  Created by wenhuanhuan on 2020/10/14.
//  Copyright ? 2020 weiman. All rights reserved.
//

#import "WMBXOperation.h"

@interface WMBXOperation()
{
    BOOL executing;
    BOOL finished;
}
@end

@implementation WMBXOperation

//重寫init方法
-(instancetype)init{
    if (self = [super init]) {
        executing = NO;
        finished = NO;
    }
    return self;
}

//重寫start方法
-(void)start{
    //如果任務(wù)被取消了
    if (self.isCancelled) {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    
    [self willChangeValueForKey:@"isExecuting"];
    //使用NSThread開辟子線程執(zhí)行main方法
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

//重寫main方法
-(void)main {
    NSLog(@"main 開始");
    @try {
        // 必須為自定義的 operation 提供 autorelease pool,因為 operation 完成后需要銷毀。
        @autoreleasepool {
            BOOL isfinish = NO;
            while (isfinish==NO&&self.isCancelled==NO) {
                NSLog(@"線程: %@", [NSThread currentThread]);
                isfinish = YES;
            }
            [self completeOperation];
        }
    } @catch (NSException *exception) {
        NSLog(@"異常:%@",exception);
    } @finally {
        NSLog(@"嘿嘿");
    }
    
    NSLog(@"main 結(jié)束");
}

- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    
    executing = NO;
    finished = YES;
    
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

-(BOOL)isAsynchronous{
    return YES;
}

-(BOOL)isExecuting{
    return executing;
}

-(BOOL)isFinished{
    return finished;
}

-(BOOL)wmStart:(NSOperation *)op{
    BOOL run = NO;
    if ([op isReady]&&![op isCancelled]) {
        if ([op isAsynchronous]==NO) {
            [op start];
        }else{
            [NSThread detachNewThreadSelector:@selector(start) toTarget:op withObject:nil];
        }
        run = YES;
    }else if ([op isCancelled]){
        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
        executing = NO;
        finished = YES;
        [self didChangeValueForKey:@"isExecuting"];
        [self didChangeValueForKey:@"isFinished"];
        run = YES;
    }
    
    return run;
}

@end

測試:

- (IBAction)test3:(id)sender {
    WMOperation * wmOp = [[WMOperation alloc] init];
    [wmOp start];
    
    //自定義串行
    WMCXOperation * cxOp = [[WMCXOperation alloc] init];
    NSLog(@"任務(wù)開始");
    [cxOp start];
    NSLog(@"任務(wù)結(jié)束");
    
    //自定義并行
    WMBXOperation * bxOp = [[WMBXOperation alloc] init];
    NSLog(@"并行任務(wù)開始");
    [bxOp wmStart:bxOp];
    NSLog(@"并行任務(wù)結(jié)束");
}

打印結(jié)果:


image.png

從結(jié)果可以看出,是在新的子線程中執(zhí)行的任務(wù)。
當(dāng)然了,因為我們再自定義類的代碼中使用了NSthread的開啟子線程的方法:


image.png
2.2 NSOperationQueue的使用

NSOperationQueue相當(dāng)于GCD的隊列。NSOperation有兩種隊列:

1.主隊列:mainQueue,在主隊列中的任務(wù)都在主線程執(zhí)行。
2.其他隊列:非主隊列通過設(shè)置最大并發(fā)數(shù)確定是串行還是并發(fā)隊列。

//主隊列
- (IBAction)mainQ:(id)sender {
    NSOperationQueue * q1 = [NSOperationQueue mainQueue];
}

//非主隊列
- (IBAction)otherQ:(id)sender {
    NSOperationQueue * q2 = [[NSOperationQueue alloc] init];
}

NSOperationQueue的作用

NSOperation的子類對象是通過調(diào)用start方法來啟動任務(wù)的。如果將對象添加到NSOperationQueue中,就不需要手動啟動了。

添加任務(wù)到隊列的兩個方法:
-(void)addOperation:(NSOperation *)op;
-(void)addOperationWithBlock:(void (^)(void))block;

下面,我們就把任務(wù)和隊列結(jié)合起來,實現(xiàn)多線程。

2.3 NSOperation子類和NSOperationQueue結(jié)合使用創(chuàng)建多線程

NSBlockOperation 不論封裝操作還是追加操作都是異步并發(fā)執(zhí)行

1》NSBlockOperation+主隊列

//block+主隊列
- (IBAction)blockAndMain:(id)sender {
    //1.創(chuàng)建主隊列
    NSOperationQueue * q1 = [NSOperationQueue mainQueue];
    //2.創(chuàng)建任務(wù)
    NSBlockOperation * p1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)一,當(dāng)前線程:%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)一結(jié)束");
    }];
    [p1 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任務(wù)二,線程:%@",[NSThread currentThread]);
    }];
    [p1 addExecutionBlock:^{
        NSLog(@"任務(wù)三,線程:%@",[NSThread currentThread]);
    }];
    //3.把任務(wù)添加到隊列
    [q1 addOperation:p1];
    
    //也可以直接添加操作到隊列中
    [q1 addOperationWithBlock:^{
        NSLog(@"直接添加操作,%@",[NSThread currentThread]);
    }];
}

結(jié)果:


image.png

從結(jié)果可以看出,三個操作是并發(fā)執(zhí)行的,雖然是在主隊列中添加任務(wù),但是任務(wù)并不是都在主線程執(zhí)行,而是有在主線程也有在子線程中并發(fā)執(zhí)行的。

2》NSBlockOperation+非主隊列

//block+非主隊列
- (IBAction)blockAndOther:(id)sender {
    //創(chuàng)建非主隊列
    NSOperationQueue * q1 = [[NSOperationQueue alloc] init];
    NSBlockOperation * b1 = [[NSBlockOperation alloc] init];
    [b1 addExecutionBlock:^{
        NSLog(@"任務(wù)一,線程:%@",[NSThread currentThread]);
    }];
    [b1 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二,線程:%@",[NSThread currentThread]);
    }];
    [b1 addExecutionBlock:^{
        NSLog(@"任務(wù)三,線程:%@",[NSThread currentThread]);
    }];
    //把任務(wù)添加到隊列
    [q1 addOperation:b1];
}

結(jié)果:

image.png

其實,我們只使用NSBlockOperation也是可以實現(xiàn)多線程的,只是需要我們自己手動開啟任務(wù)。

//block+非主隊列
- (IBAction)blockAndOther:(id)sender {
    //創(chuàng)建非主隊列
    NSOperationQueue * q1 = [[NSOperationQueue alloc] init];
    NSBlockOperation * b1 = [[NSBlockOperation alloc] init];
    [b1 addExecutionBlock:^{
        NSLog(@"任務(wù)一,線程:%@",[NSThread currentThread]);
    }];
    [b1 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二,線程:%@",[NSThread currentThread]);
    }];
    [b1 addExecutionBlock:^{
        NSLog(@"任務(wù)三,線程:%@",[NSThread currentThread]);
    }];
    //把任務(wù)添加到隊列
    [q1 addOperation:b1];
    
    //只使用NSBlockOperation
    NSLog(@"-------只使用NSBlockOperation實現(xiàn)------------");
    NSBlockOperation * b2 = [[NSBlockOperation alloc] init];
    [b2 addExecutionBlock:^{
        NSLog(@"1,線程:%@",[NSThread currentThread]);
    }];
    [b2 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"2,線程:%@",[NSThread currentThread]);
    }];
    [b2 addExecutionBlock:^{
        NSLog(@"3,線程:%@",[NSThread currentThread]);
    }];
    [b2 start];
}

結(jié)果:


image.png

3》NSInvocationOperation+主隊列

- (IBAction)InvoAndMain:(id)sender {
    NSOperationQueue * mainQ = [NSOperationQueue mainQueue];
    NSInvocationOperation * p1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    NSInvocationOperation * p2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
    NSInvocationOperation * p3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task3) object:nil];
    [mainQ addOperation:p1];
    [mainQ addOperation:p2];
    [mainQ addOperation:p3];
}

-(void)task1 {
    NSLog(@"任務(wù)一, %@",[NSThread currentThread]);
}

-(void)task2 {
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"任務(wù)二, %@",[NSThread currentThread]);
}

-(void)task3 {
    NSLog(@"任務(wù)三, %@",[NSThread currentThread]);
}

結(jié)果:


image.png

任務(wù)在主隊列順序執(zhí)行,也就是串行。

4》NSInvocationOperation+非主隊列

- (IBAction)invoAndOther:(id)sender {
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation * p1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    NSInvocationOperation * p2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
    NSInvocationOperation * p3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task3) object:nil];
    [queue addOperation:p1];
    [queue addOperation:p2];
    [queue addOperation:p3];
}

-(void)task1 {
    NSLog(@"任務(wù)一, %@",[NSThread currentThread]);
}

-(void)task2 {
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"任務(wù)二, %@",[NSThread currentThread]);
}

-(void)task3 {
    NSLog(@"任務(wù)三, %@",[NSThread currentThread]);
}

結(jié)果:


image.png

由結(jié)果可以看出,三個任務(wù)并發(fā)執(zhí)行。

3. NSOperation其他重要用法
3.1 NSOperation的依賴 - (void)addDependency:(NSOperation *)op;

有時候,我們需要給任務(wù)添加依賴關(guān)系,比如有兩個任務(wù)op1和op2,任務(wù)2必須要等待任務(wù)1執(zhí)行完成之后才能執(zhí)行,這個時候就可以添加任務(wù)2依賴于任務(wù)1 .

//任務(wù)依賴
- (IBAction)test1:(id)sender {
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    NSBlockOperation * op1 = [[NSBlockOperation alloc] init];
    [op1 addExecutionBlock:^{
        NSLog(@"任務(wù)一, %@",[NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op2 = [[NSBlockOperation alloc] init];
    [op2 addExecutionBlock:^{
        NSLog(@"op2哦,%@",[NSThread currentThread]);
    }];
    //設(shè)置op2依賴于任務(wù)op1
    //[op2 addDependency:op1];
    [queue addOperation:op1];
    [queue addOperation:op2];
}

我們在這里有連個任務(wù),op1和op2,兩個任務(wù)都在非主隊列中執(zhí)行,我們先不添加依賴,看看執(zhí)行結(jié)果:


image.png

從結(jié)果可以看出,任務(wù)一有兩個操作,任務(wù)二有一個操作,它們分別在不同的線程中執(zhí)行,不分先后。
添加依賴看看:


image.png

打印結(jié)果:
image.png

從結(jié)果可以看出,添加了依賴之后,任務(wù)二只會在任務(wù)一執(zhí)行完成之后才會執(zhí)行。

3.2 NSOperation執(zhí)行完成 completionBlock

如果想要在某個操作執(zhí)行完成之后在執(zhí)行某種操作,這個時候就可以使用completionBlock了。

//NSOperation執(zhí)行完成
- (IBAction)test2:(id)sender {
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)一,%@",[NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二,%@",[NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任務(wù)三,%@",[NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        NSLog(@"任務(wù)四,%@",[NSThread currentThread]);
    }];
    op1.completionBlock = ^{
        NSLog(@"任務(wù)都執(zhí)行完成啦");
    };
    [queue addOperation:op1];
}

打印結(jié)果:


image.png

從結(jié)果可以看出,四個任務(wù)在四個子線程中完成,直到所有的任務(wù)都執(zhí)行完,才執(zhí)行了completionBlock內(nèi)的代碼。

3.3 最大并發(fā)數(shù) maxConcurrentOperationCount

我們可以設(shè)置隊列的最大并發(fā)數(shù)屬性,控制隊列是并發(fā)、串行還是不執(zhí)行。
maxConcurrentOperationCount>1: 并發(fā)
maxConcurrentOperationCount=1:串行
maxConcurrentOperationCount=-1:不限制,默認(rèn)值
maxConcurrentOperationCount=0:不會執(zhí)行任何操作
我們一起來試試。

  • 串行
//maxConcurrentOperationCount 最大并發(fā)數(shù)
- (IBAction)test3:(id)sender {
    //串行
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)一,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)二,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)三,%@",[NSThread currentThread]);
    }];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}

打印結(jié)果:


image.png

只開辟了一個線程,任務(wù)一個個的執(zhí)行。我們把任務(wù)二改成耗時操作,看看會不會阻塞當(dāng)前線程。


image.png

看看打印結(jié)果:


image.png

依然是順序執(zhí)行,說明會阻塞當(dāng)前線程。

注意:如果我們是一個操作中的三個子任務(wù),我們設(shè)置隊列的最大并發(fā)數(shù)是沒有效果的,例如:

//2. 一個操作的多個任務(wù),設(shè)置最大并發(fā)數(shù)為1是沒有效果的
    NSOperationQueue * queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;
    NSBlockOperation * op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1,%@",[NSThread currentThread]);
    }];
    [op4 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"2,%@",[NSThread currentThread]);
    }];
    [op4 addExecutionBlock:^{
        NSLog(@"3,%@",[NSThread currentThread]);
    }];
    [queue2 addOperation:op4];

結(jié)果:


image.png

我們發(fā)現(xiàn),即使我們設(shè)置了隊列的最大并發(fā)數(shù)為1,由于我們只有一個操作,這個操作中有三個任務(wù),這三個任務(wù)還是在三個線程中執(zhí)行的,也不是順序的。由此可見,最大操作數(shù)針對的是操作,也就是NSBlockOperation對象,而不是任務(wù)。

  • 并行
    我們設(shè)置最大并發(fā)數(shù)大于1,看看結(jié)果。
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 5;
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)一,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)三,%@",[NSThread currentThread]);
    }];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

打印結(jié)果:


image.png

我們發(fā)現(xiàn)三個任務(wù)是并發(fā)執(zhí)行的,符合我們的預(yù)期。

  • 不執(zhí)行操作,設(shè)置為0
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 0;
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)一,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任務(wù)二,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)三,%@",[NSThread currentThread]);
    }];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

當(dāng)我們設(shè)置了
queue.maxConcurrentOperationCount = 0;
的時候,發(fā)現(xiàn)不會有任何打印,操作都不再執(zhí)行。

3.4 隊列暫停 suspended

當(dāng)我們設(shè)置了suspended=YES之后,隊列就會暫停。

//隊列暫停,suspended
- (IBAction)test4:(id)sender {
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    queue.suspended = YES
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任務(wù)一,%@",[NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        NSLog(@"任務(wù)二開始,%@",[NSThread currentThread]);
        for (int i=0; i<10; i++) {
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"2: i=%d",i);
        }
        NSLog(@"任務(wù)二結(jié)束");
    }];
    [queue addOperation:op1];
}

這個時候,當(dāng)我們點擊按鈕,是不會有任何操作執(zhí)行的,因為隊列暫停了。

3.5 取消隊列的未執(zhí)行的所有操作
@interface OperationOtherController ()

@property(nonatomic, strong)NSOperationQueue * myQueue;

@end
//取消所有任務(wù)
- (IBAction)test5:(id)sender {
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作一:1,%@",[NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        NSLog(@"操作一:2,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作二:1,%@",[NSThread currentThread]);
    }];
    NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"操作三:1,%@",[NSThread currentThread]);
    }];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    self.myQueue = queue;
}
- (IBAction)cancelTest5:(id)sender {
    [self.myQueue cancelAllOperations];
}

打印結(jié)果:


image.png

我們發(fā)現(xiàn),即使我們點擊了取消按鈕,未執(zhí)行的操作三并沒有被取消。

注意:暫停和取消只能暫停或取消處于等待狀態(tài)的任務(wù),不能暫?;蛉∠趫?zhí)行中的任務(wù),必須等正在執(zhí)行的任務(wù)執(zhí)行完畢之后才會暫停,如果想要暫?;蛘呷∠趫?zhí)行的任務(wù),可以在每個任務(wù)之間即每當(dāng)執(zhí)行完一段耗時操作之后,判斷是否任務(wù)是否被取消或者暫停。如果想要精確的控制,則需要將判斷代碼放在任務(wù)之中,但是不建議這么做,頻繁的判斷會消耗太多時間

3.6 打印隊列中所有的操作對象
- (IBAction)test6:(id)sender {
    NSLog(@"打印所有操作");
    
    NSLog(@"%@",self.myQueue.operations);
}

結(jié)果:


image.png
  1. 案例:下載圖片并合成
//
//  OperationCaseController.m
//  Multithreading
//
//  Created by wenhuanhuan on 2020/10/16.
//  Copyright ? 2020 weiman. All rights reserved.
//

#import "OperationCaseController.h"

@interface OperationCaseController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV1;
@property (weak, nonatomic) IBOutlet UIImageView *imageV2;
@property (weak, nonatomic) IBOutlet UIImageView *imageVFinal;
@property(nonatomic,strong)UIImage * image1;
@property(nonatomic,strong)UIImage * image2;
@end

@implementation OperationCaseController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (IBAction)startAction:(id)sender {
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    //下載圖片一
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638749466&di=92ace2ffa924fe6063e7a221729006b1&imgtype=0&src=http%3A%2F%2Fpic.autov.com.cn%2Fimages%2Fcms%2F20119%2F6%2F1315280805177.jpg";
        UIImage * image = [self downLoadImage:str];
        self.image1 = image;
        //回到主線程,顯示圖片
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageV1.image = image;
        }];
    }];
    //下載圖片二
    NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSString * str = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1601638873771&di=07129fd95c56096a4282d3b072594491&imgtype=0&src=http%3A%2F%2Fimg.51miz.com%2Fpreview%2Felement%2F00%2F01%2F12%2F49%2FE-1124994-5FFE5AC7.jpg";
        UIImage * image = [self downLoadImage:str];
        self.image2 = image;
        //回到主線程,顯示圖片
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageV2.image = image;
        }];
    }];
    //合成圖片
    NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
        UIImage * image = [self makeImage:self.image1 image2:self.image2];
        //回到主線程,顯示圖片
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageVFinal.image = image;
        }];
    }];
    //由于合成圖片要在圖片一和圖片二完成之后才能進行,所以需要添加依賴
    [op3 addDependency:op1];
    [op3 addDependency:op2];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}

-(UIImage *)downLoadImage:(NSString *)str {
    NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    NSURL * url = [NSURL URLWithString:str];
    NSData * data = [NSData dataWithContentsOfURL:url];
    UIImage * image = [UIImage imageWithData:data];
    return image;
}

-(UIImage *)makeImage:(UIImage *)image1 image2:(UIImage *)image2 {
    //圖形上下文開啟
    UIGraphicsBeginImageContext(CGSizeMake(300, 200));
    
    //圖形二
    [image2 drawInRect:CGRectMake(0, 0, 300, 200)];
    //圖形一
    [image1 drawInRect:CGRectMake(100, 50, 100, 100)];
    //獲取新的圖片
    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
    //關(guān)閉上下文
    UIGraphicsEndImageContext();
    return image;
}

@end

運行結(jié)果:


image.png

以上就是關(guān)于NSOperation創(chuàng)建多線程的探究內(nèi)容了,如有錯漏還請指教。
祝大家生活愉快。

最后編輯于
?著作權(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ù)。

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