GCD详解

Grand Central Dispatch(GCD)

1. 什么是GCD

​ 异步执行任务的技术之一,开发者只需定义想执行的任务,并追加到适当的Dispatch Queue中,GCD就成生成必要的线程并计划执行任务。且该方法比单纯使用线程更有效率(gcd的线程管理是系统实现,统一管理)

2.GCD的API

​ 苹果官方对GCD的说明:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中

2.1Dispatch Queue是什么?

​ 执行处理的等待队列,开发者通过GCD的API将任务(通常是block代码块)添加到Dispatch Queue。 Dispatch Queue按照追加顺序(先进先出),执行处理。

- [外链图片转存失败(img-Tid6FVqm-1566751827888)(Grand Central Dispatch(GCD)].assets/image-20190820123404741.png)

​ 在GCD中有两种队列,分别是

  • 串行队列(Serial Dispatch Queue)

  • 并发队列(Concurrent Dispatch Queue)
    [外链图片转存失败(img-JMmwFtuC-1566751827889)(Grand Central Dispatch(GCD)].assets/image-20190820125233437.png)

同时我们需要提前了解,async是异步执行,sync是同步执行

同步执行sync异步执行async
Serial串行队列当前线程,顺序执行其它线程,顺序执行
Concurrent并行队列当前线程,顺序执行很多线程,并行执行

2.2如何获取Dispatch Queue

2.2.1通过GCD的API获取 dispatch_queue_create
//第一个参数表示队列的唯一标识符,用于 DEBUG,可为空。队列的名称推荐使用应用程序 ID 这种逆序全程域名。
//第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。 为NULL生成serial
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.serialDispatchQueue", NULL);
        dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.concurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

​ 虽然说Serial Dispatch Queue 串行处理队列,同时只能执行一个追加处理,但是可以生成多个队列进行同时处理

在这里插入图片描述
需要注意的是,每生成一个Serial Dispatch Queue,系统就会开一个线程对应处理

另外多线程也会带来线程安全,数据竞争的问题

​ 想要并行执行且不发生数据竞争的问题就需要 Concurrent Dispatch Queue,通常使用dispatch_queue_create生成的Dispatch Queue在使用结束后通过

dispatch_release(mySerialDispatchQueue);
//同时也有retain函数
dispatch_retain(mySerialDispatchQueue);
2.2.2Main Dispatch Queue/Global Dispatch Queue

​ 第二种方法是获取系统提供的标准的Dispatch Queue。系统提供的DispatchQueue。 mainDispatchQueue是主线程(只有一个)执行的Queue,所以自然是Serial Dispatch Queue

​ Global Dispatch Queue是所有程序都能使用的Concurrent Dispatch Queue

有四个优先级

[外链图片转存失败(img-6Nu47zPh-1566751827889)(Grand Central Dispatch(GCD)].assets/image-20190820013116090-6271336.png)

Main Dispatch Queue/Global Dispatch Queue的获取方法:

dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

高优先级:DISPATCH_QUEUE_PRIORITY_HIGH

默认优先级:DISPATCH_QUEUE_PRIORITY_DEFAULT

低优先级:DISPATCH_QUEUE_PRIORITY_LOW

后台优先级:DISPATCH_QUEUE_PRIORITY_BACKGROUND

dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

对于main dispatch queue 和 global dispatch queue执行dispatch_retain和dispatch_release函数不会引起任何变化,也不会存在任何问题。

这也是使用标准 Dispatch Queue的好处

//默认优先级Global Dispatch Queue执行block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //并行执行的处理
    dispatch_async(dispatch_get_main_queue(), ^{
        //只能主线程中执行的处理
    });
});

2.3dispatch_set_target_queue

​ dispatch_set_target_queue用来变更Dispatch Queue执行的优先级

dispatch_queue_t muSerialDispatchQueue = dispatch_queue_create("com.example.gcd.serialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//第一个参数:指定要变更优先级的Dispatch Queue
//第二个参数:指定一个队列,使用与其相同执行优先级
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

//可将多个并行的Serial Dispatch Queue用dispatch_set_target_queue指定其中某一个Serial Dispatch Queue,防止处理并行执行

2.4dispatch_after

​ 在指定的时间后,将任务追加到Dispatch Queue中。

//从现在开始到3秒后
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
//第一个参数:指定时间,dispatch_time_t类型,用dispatch_time或者dispatch_walltime函数生成
//第二个参数:指定要追加处理的Dispatch Queue
//第三个参数:指定要执行的Block
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"waited at least tree seconds.");
});

上文代码在3秒后用dispatch 将追加Block到Main Dispatch Queue当中,Main Dispatch Queue在主线程的RunLoop中执行,假设每隔1/60秒可以轮询一次,那么Block最快3秒后执行,最慢时3秒+1/60秒后执行。

2.5Dispatch Group

​ Dispatch Queue中的处理全部结束,想执行结束处理,如果只使用一个Serial Dispatch Queue时,只要将向执行的处理全部追加到该Serial Dispatch Queue中并在最后追加结束处理,即可实现。 但是使用Concurrent Dispatch Queue时,同时使用多个Dispatch Queue,将会变得灰常发咋。

​ 这种情况需要使用Dispatch Group。例如:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create(); //由create可知,还具有release,retain方法

//dispatch_group_async与dispatch_async相同,都是追加处理到Dispatch Queue当中,不过会指定一个参数 group,指定的Block属于指定的Dispatch Group
dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
        
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});
dispatch_release(group);

//输出如下:
blk1
blk2
blk0
done

​ blk的输出顺序并不确定,但是done一定是最后输出。 无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可见识这些处理执行的结束。一旦检测到结束,就可讲结束的处理追加到Dispatch Queue当中。

​ 在追加到Dispatch Group中处理全部直接结束后,会执行dispatch_group_notify,将会把指定的Block追加到Dispatch Queue当中。

​ 在Dispatch Group中也可以使用dispatch_group_wait函数仅等待全部处理执行结束。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create(); 

dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER); //永远等待,直到处理全部结束
dispatch_release(group);
//如果等待一秒 那么如下处理
dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC)
long result = dispatch_group_wait(group, time);

//这一行已经过了指定时间,返回0表示全部处理结束,非0未结束
if (result == 0) {
	//属于Dispatch Group的全部处理执行结束
} else {
    //属于Dispatch Group的某一个处理还在执行中
}

或者使用队列组的 dispatch_group_enterdispatch_group_leave 组合来实现 dispatch_group_async

dispatch_group_enter标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1

dispatch_group_leave标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。

当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait解除阻塞,以及执行追加到 dispatch_group_notify中的任务。

/**
 * 队列组 dispatch_group_enter、dispatch_group_leave
 */
- (void)groupEnterAndLeave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];          // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);  // 打印当前线程

        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];          // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);  // 打印当前线程
        
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        [NSThread sleepForTimeInterval:2];          // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);  // 打印当前线程
    
        NSLog(@"group---end");
    });
}

2.6dispatch_barrier_async

​ 通常我们读写处理的方法是: 将读取处理追加到Concurrent Dispatch Queue当中,写入处理(任一个读取处理没有执行的状态下)追加到Serial Dispatch Queue(写入处理结束前,读取处理不可执行)

​ 行之有效的方法是使用dispatch_barrier_async

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.serialDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
/*
 * 如在这里执行写入处理,写入的内容被之后的任务读取
 * dispatch_async(queue, blk3_for_writing);  将发生不可预期的错误
 * 需要使用dispatch_barrier_async
 * 会等待这里之上的所有并行结束后,将指定的处理追加到Concurrent Dispatch Queue 当中,然后由dispatch_barrier_async追加的处理执行完毕后,Concurrent Dispatch Queue 才恢复并行动作
 */
dispatch_async(queue, blk3_for_reading);
dispatch_async(queue, blk4_for_reading);

dispatch_release(queue);

[外链图片转存失败(img-gRYnKZeb-1566751827889)(Grand Central Dispatch(GCD)].assets/image-20190820152905581.png)

注意:这里阻塞的是Concurrent队列,而不是线程

dispatch_barrier_sync会阻塞队列,并且阻塞线程

2.7dispatch_sync

  • dispatch_async 异步追加任务,该函数不做任何等待
  • dispatch_sync 同步追加任务,再将Block追加结束前,该函数会一直等待
  • dispatch_sync 使用不恰当使用会导致死锁
//主队列死锁
(void) syncMain() {
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"hello");}); 
//下面处理也一样
dispatch_async(queue, ^{
  dispatch_sync(queue, ^{NSLog(@"hello");}); 
});
}

主线程执行会产生死锁

分析:在主线程执行syncMain方法,相当于把syncMain任务放到了主线程队列中,同步执行会等待当前任务执行完毕才会接着执行,然后当我们追加了任务到主队列中,新任务就等主线程处理syncMain任务,而syncMain任务需要等新任务执行完毕才能继续执行,互相等待,产生死锁。

当代码执行到dispatch_sync时候,会立马阻塞主线程,然后把Block的任务放到main_queue中,main_queue的任务会被取出来放到主线程中执行,可是主线程被阻塞,所以Block任务不能完成,它不完成,dispatch_sync就一直阻塞主线程。

//嵌套队列, 串行队列+异步执行  也会产生死锁
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
NSLog("开始 - %@", NSThread.currentThread())
dispatch_async(queue, ^{    // 异步执行 + 串行队列
    NSLog("sync之前 - %@", NSThread.currentThread())
    dispatch_sync(queue, ^{  // 同步执行 + 当前串行队列
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog("sync中 - %@", NSThread.currentThread())
    });
    NSLog("sync之后 - %@", NSThread.currentThread())
});
NSLog("结束 - %@", NSThread.currentThread())

[外链图片转存失败(img-iv01G9rO-1566751827889)(Grand Central Dispatch(GCD)].assets/image-20190820193847853.png)

分析:可见没有执行async中的 “sync中” 和“sync后”两句。 而我们知道async异步添加任务,会开辟一个新的线程,此时有两个线程, 一个是主线程 执行了“开始 -” “结束 -” 而在async中开辟的新线程先执行 “sync之前”, 紧接着执行 dispatch_sync:

高潮来了:“dispatch_sync”同步执行,那么会先阻塞自己所在线程去将block放在queue当中,然后去执行queue中添加的任务, 但是由于我们之前执行了async给串行队列中已经添加一条任务是去执行dispatch_sync, 两个任务相互等待阻塞,导致死锁。

为何同步执行串行的队列不会死锁

- (void)syncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    dispatch_sync(queue, ^{
        // 追加任务 3
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    NSLog(@"syncSerial---end");
}

首先我们要清楚,主线程一直拥有一个主队列,执行同步的串行队列时, 先执行syncSerial,会将当前任务放在主队列当中,然后sync中的block每次会被依次添加到创建的自定义的Serial队列中,并在加入的时候立即执行,直到执行完毕,自定义队列任务就完成了,执行完毕后会告知主队列syncSerial执行完成,主队列任务完成。

可看出主队列就一个任务syncSerial,其它任务都在串行队列中

dispatch_barrier_async作用:

  1. 等待追加的处理全部执行结束后

    1. 再追加处理到Dispatch Queue中

2.8dispatch_apply

​ dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,将函数按指定的次数将指定的Block追加到Dispatch Queue中, 并等待全部处理执行结束

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//参数1: 重复次数
//参数2: 追加任务的队列
//参数3: 带参数的Block,区分各个Block
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
});
NSLog(@"done");

[外链图片转存失败(img-zhpvco7I-1566751827890)(Grand Central Dispatch(GCD)].assets/image-20190820154425630.png)

dispatch_apply函数与dispatch_sync函数相同,会等待处理执行结束,推荐在dispatch_async函数中非同步的执行dispatch_apply函数

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//在global queue中非同步执行
dispatch_async(queue, ^{
    //global queue等待dispatch_apply函数全部处理执行结束
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu", index);
    });

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"done");
    });
});

2.9dispatch_suspend/dispatch_resume 挂起/恢复

  • dispatch_suspend(queue); 挂起指定的Dispatch Queue

  • dispatch_resume(queue); 恢复指定的Dispatch Queue

    相当于暂停播放按钮

2.10 Dispatch Semaphore gcd中信号量

​ Dispatch Semaphore持有计数的信号,该计数是多线程编程中的计数类型信号。

信号量为0阻塞,大于0不阻塞,通过改变信号量的值来同步线程

初始为1的信号量

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1;)

函数等待Dispatch Semaphore的计数值大于或者等于1,满足条件时,对该计数做减法并返回

dispatch_semphore_wait(semphore, DISPATCH_TIME_FOREVER);

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
    //这里计数值减去1
    //做相应处理
} else {
    //不满足条件,一直等待
}

//计数值加1的操作
dispatch_semaphore_signal(semaphore);
//线程同步
- (void)semaphoreSync {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}

2.11 dispatch_once

​ dispatch_once函数保证程序执行中只执行一次指定处理的AIPI。 例如初始化

static int initialized = NO;
if (initialized == NO) {
    initialized = YES;
}
使用dispatch_once函数,源码写为:
static dispatch_once_t pred;
dispatch_once(&pred, ^{
    //初始化
})

通过dispatch_once函数,初始化在多线程环境下执行,可保证百分之百的安全


参考文献

https://www.jianshu.com/p/2d57c72016c6

《Objective-C高级编程 iOS与OS X多线程和内存管理》

https://www.jianshu.com/p/0b0d9b1f1f19


NSThread

创建线程

//创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 启动
[thread start];

//创建并启动
[self performSelectorInBackground:@selector(run:) withObject:nil];

/***************一些其它方法****************/
//取消线程
- (void)cancel;

//启动线程
- (void)start;

//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//获取当前线程信息
+ (NSThread *)currentThread;

//获取主线程信息
+ (NSThread *)mainThread;

//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

NSOperation和NSOperationQueue

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务(操作)队列(操作队列)

  • 操作(Operation):
    • 执行操作的意思,换句话说就是你在线程中执行的那段代码。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperationNSBlockOperation,或者自定义子类来封装操作。
  • 操作队列(Operation Queues):
    • 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
    • 操作队列通过设置**最大并发操作数(maxConcurrentOperationCount)**来控制并发、串行。
    • NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。

操作步骤:

  1. 将要执行的任务封装到一个 NSOperation 对象中。
  2. 将此任务添加到一个 NSOperationQueue 对象中。

NSOperation只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperationNSBlockOperation。创建一个 Operation 后,需要调用 start方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel方法即可。

  • NSInvocationOperation : 需要传入一个方法名。

1.创建操作

1.1添加任务:NSInvocationOperation

//1.创建NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

//2.开始执行
[operation start];
- (void)run {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}

1.2添加任务:NSBlockOperation

​ 默认会在当前线程执行,但是通过方法addExecutionBlock(可选)添加执行多个Block,Operation任务会多线程并发执行。

//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
	NSLog(@"%@", [NSThread currentThread]);
}];

//添加多个Block
for (NSInteger i = 0; i < 5; i++) {
	[operation addExecutionBlock:^{
		NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
	}];
}

//2.开始任务
[operation start];

[外链图片转存失败(img-CVXIHxNL-1566751827890)(Grand Central Dispatch(GCD)].assets/image-20190820213357976.png)

NOTEaddExecutionBlock必须 在start之前执行,否则会报错

1.3自定义Operation实现

​ 可以通过重写 main 或者 start 方法 来定义自己的 NSOperation 对象。重写main方法比较简单,我们不需要管理操作的状态属性 isExecutingisFinished。当 main执行完返回的时候,这个操作就结束了。

先定义一个继承自 NSOperation 的子类,重写main方法。

// YSCOperation.h 文件
#import <Foundation/Foundation.h>

@interface YSCOperation : NSOperation

@end

// YSCOperation.m 文件
#import "YSCOperation.h"

@implementation YSCOperation

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

@end

使用时候倒入头文件

/**
 * 使用自定义继承自 NSOperation 的子类
 */
- (void)useCustomOperation {
    // 1.创建 YSCOperation 对象
    YSCOperation *op = [[YSCOperation alloc] init];
    // 2.调用 start 方法开始执行操作
    [op start];
}

[外链图片转存失败(img-SEtPoTN0-1566751827890)(Grand Central Dispatch(GCD)].assets/image-20190821003518864.png)

没有使用NSOperationQueue、在主线程单独使用自定义继承自 NSOperation 的子类的情况下,是在主线程执行操作,并没有开启新线程。

2.创建队列

  • 主队列
    • 凡是添加到主队列中的操作,都会放到主线程中执行。
  • 定义队列(非主队列)
    • 添加到这种队列中的操作,就会自动放到子线程中执行。
    • 同时包含了:串行、并发功能。
//主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

2.1将操作加入队列当中(并发执行)

第一种方法- (void)addOperation:(NSOperation *)op;

​ 需要先创建操作,再将创建好的操作加入到创建好的队列中去

/**
 * 使用 addOperation: 将操作加入到操作队列中
 */
- (void)addOperationToQueue {
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    // 使用 NSBlockOperation 创建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}

第二种方法:- (void)addOperationWithBlock:(void (^)(void))block;

​ 无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中

/**
 * 使用 addOperationWithBlock: 将操作加入到操作队列中
 */

- (void)addOperationWithBlockToQueue {
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.使用 addOperationWithBlock: 添加操作到队列中
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}

3.NSOperationQueue 控制串行执行、并发执行

这里有个关键属性 maxConcurrentOperationCount,叫做最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行。

注意:这里 maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。

最大并发操作数:maxConcurrentOperationCountmaxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。

/**
 * 设置 MaxConcurrentOperationCount(最大并发操作数)
 */
- (void)setMaxConcurrentOperationCount {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.设置最大并发操作数
    queue.maxConcurrentOperationCount = 1; // 串行队列
// queue.maxConcurrentOperationCount = 2; // 并发队列
// queue.maxConcurrentOperationCount = 8; // 并发队列

    // 3.添加操作
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}

4. NSOperation 操作依赖

​ 通俗来讲,如果是B依赖A,那么会先执行A,完成后再执行B。

​ NSOperation为我们提供了三个接口

  • - (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
  • - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

参考文献:

https://bujige.net/blog/iOS-Complete-learning-NSOperation.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值