OC底层原理27:GCD之 NSThread & GCD & NSOperation

张建 lol

前言

本文主要目的是介绍 NSThread、GCD、NSOperation 常见的使用方式

NSThread

NSThread 是苹果官方提供 面向对象 的线程操作技术,是对 thread 的上层封装,比较偏向于底层,简单方便,可以直接操作线程对象,使用频率较少

创建线程

线程的创建方式主要有以下三种方式

  • 通过 init 初始化方式创建,需要手动启动

  • 通过 detachNewThreadSelector 构造器方式创建

  • 通过 performSelector... 方式创建,主要是用于获取 主线程,以及 后台线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)createNSThread{

// 方式一:初始化方式,需要手动启动
NSString * threadName1 = @"NSThread1";
NSThread * thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1];
[thread1 start];

// 方式二:构造器方式,自动启动
NSString * threadName2 = @"NSThread2";
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2];

// 方式三:performSelector...方法创建
NSString * threadName3 = @"NSThread3";
[self performSelectorInBackground:@selector(doSomething:) withObject:threadName3];

// 方式四:在主线程执行
NSString * threadName4 = @"NSThread4";
[self performSelectorOnMainThread:@selector(doSomething:) withObject:threadName4 waitUntilDone:YES];
}
- (void)doSomething:(NSObject *)objc{
NSLog(@"%@ - %@", objc, [NSThread currentThread]);
}

属性

1
2
3
4
5
- thread.executing        // 线程是否在执行
- thread.isCancelled // 线程是否取消
- thread.isFinished // 线程是否完成
- thread.isMainThread // 是否是主线程
- thread.threadPriority // 线程的优先级,取值返回0.0~1.0默认优先级是0.5,1.0便是最高优先级,优先级越高,CPU调度的评率高

类方法

常用的类方法有以下几个:

  • currentThread:获取当前线程

  • sleep…:阻塞线程

  • exit:退出线程

  • mainThread:获取主线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)threadClassMethod{
// 当前线程
[NSThread currentThread];
// 如果number = 1,则表示在主线程,否则在子线程
NSLog(@"%@",[NSThread currentThread]);

// 阻塞休眠
[NSThread sleepForTimeInterval:2]; // 休眠2s
[NSThread sleepUntilDate:[NSDate date]]; // 休眠到指定时间

// 其他
[NSThread exit]; // 退出线程
[NSThread isMainThread]; // 判断当前线程是否为主线程
[NSThread isMultiThreaded]; // 判断当前线程是否为多线程
NSThread * mainThread = [NSThread mainThread]; // 主线程对象
NSLog(@"%@",mainThread);
}

实际使用

  1. 根据若⼲个url异步加载多张图⽚,然后在都下载完成后合成⼀张整图?GCD如何实现?

dispatch_group_t + dispatch_group_enter + dispatch_group_leave + dispatch_group_notify

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-(void)Btn{
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
// 创建线程组
dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
// enter
dispatch_group_enter(downloadGroup);
// 下载图片
NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

NSLog(@"%d---%d",i,i);
// 离开
dispatch_group_leave(downloadGroup);
}];
[task resume];
}

dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
}

dispatch_group_enter:通知group,下面的任务马上要放到group中执行了。
dispatch_group_leave:通知group,任务完成了,该任务要从group中移除了。
dispatch_group_notify:通知线程组中的任务都完成了

  1. 10个网络请求顺序回调?

dispatch_semaphore_t:信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-(void)Btn{
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {

NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

NSLog(@"%d---%d",i,i);
dispatch_semaphore_signal(sem);
}];

[task resume];
// 让循环等待
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

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

GCD

dispatch_after

1
2
3
4
5
6
7
8
9
- (void)testAfter{
/*
dispatch_after表示在某队列中的blcok延迟执行
应用场景:在主队列上延迟执行一向任务,如viewDidload之后延迟1s,提示一个alertview(是延迟加入到队列,而不是延迟执行)
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1s后输出");
});
}

dispath_once

1
2
3
4
5
6
7
8
9
10
11
- (void)testOnce{
/*
dispatch_once保证APP在运行期间,block中的代码只执行有一次
应用场景:单例、method_swizzling
*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 创建单例、method swizzled或其他服务
NSLog(@"创建单例");
});
}

dispach_apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)testApply{
/*
dispatch_apply将指定的block追加到指定的队列中重复执行,并等到全部的处理执行结束 - 相当于线程安全的for循环
应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性
- 添加到串行队列中 —— 按序执行
- 添加到主队列中 —— 死锁
- 添加到并发队列中 —— 乱序执行
- 添加都全局队列中 —— 乱序执行
*/
dispatch_queue_t queue = dispatch_queue_create("ZJ", DISPATCH_QUEUE_SERIAL);
NSLog(@"dispatch_apply前");
/*
param1:重复的次数
param2:追加的队列
param3:执行任务
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"dispatch_apply 的线程 %zu - %@", index, [NSThread currentThread]);
});

NSLog(@"dispatch_apply后");
}

dispatch_group_t

有以下两种使用方式

  • 方式一:使用 dispatch_group_t + dispatch_group_notify
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)testGroup1{
/*
dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间
应用场景:多个接口请求之后刷新页面
*/
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create(0, 0);

dispatch_group_async(group, queue, ^{
NSLog(@"请求一完成");
});

dispatch_group_async(group, queue, ^{
NSLog(@"请求二完成");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});
}

// ****打印结果****
2022-02-22 14:54:43.783327+0800 NSThread_Demo[4902:3701318] 请求一完成
2022-02-22 14:54:43.783404+0800 NSThread_Demo[4902:3701318] 请求二完成
2022-02-22 14:54:43.783432+0800 NSThread_Demo[4902:3701318] 刷新页面
  • 【方式二】使用 dispatch_group_enter + dispatch_group_leave + dispatch_group_notify
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (void)testGroup2{
/*
dispatch_group_enter 和 dispatch_group_leave 成对出现,使进出组的逻辑更加清晰
*/
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create(0, 0);

dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求一完成");
dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求二完成");
dispatch_group_leave(group);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});
}

// ****打印结果****
2022-02-22 15:13:39.981715+0800 NSThread_Demo[4906:3707145] 请求一完成
2022-02-22 15:13:39.981788+0800 NSThread_Demo[4906:3707145] 请求二完成
2022-02-22 15:13:39.987105+0800 NSThread_Demo[4906:3707122] 刷新页面
  • 在方式二的基础上增加超时 dispatch_group_wait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- (void)testGroup3{
/*
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

group:需要等待的调度组
timeout:等待的超时时间(即等多久)
- 设置为 DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕
- 设置为 DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕

返回值:为long 类型
- 返回值为0 —— 在指定时间内调度组完成了任务
- 返回值不为0 —— 在指定时间内调度组没有按时完成任务
*/

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

dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求一完成");
dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求二完成");
dispatch_group_leave(group);
});

// long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW); // 立即
// long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // 没限制
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC)); // 1s后
NSLog(@"timeout = %ld",timeout);
if (timeout == 0) {
NSLog(@"按时完成任务");
}else {
NSLog(@"超时");
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});
}

// ****打印结果****
2022-02-22 15:25:03.528855+0800 NSThread_Demo[4915:3711930] 请求一完成
2022-02-22 15:25:03.528859+0800 NSThread_Demo[4915:3711929] 请求二完成
2022-02-22 15:25:03.528923+0800 NSThread_Demo[4915:3711910] timeout = 0
2022-02-22 15:25:03.528946+0800 NSThread_Demo[4915:3711910] 按时完成任务
2022-02-22 15:25:03.534554+0800 NSThread_Demo[4915:3711910] 刷新页面

dispatch_barrier_sync & dispatch_barrier_async

栅栏函数,主要有两种使用场景:串行队列、并发队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
- (void)testBarrier{
/*
dispatch_barrier_sync & dispatch_barrier_async

应用场景:同步锁

等栅栏前追加到队列中的任务执行完毕后,再讲栅栏后的任务追加到队列中
简而言之,就是先执行栅栏前任务,在执行栅栏任务,最后执行栅栏后任务

- dispatch_barrier_async:前面的任务执行完毕才会来到这里
- dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行

- dispatch_barrier_async:可以控制队列中任务的执行顺序
- dispatch_barrier_sync:不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用)
*/
// [self testBarrier1];
[self testBarrier2];
}
- (void)testBarrier1{
// 串行队列使用栅栏函数
dispatch_queue_t queue = dispatch_queue_create("ZJ", DISPATCH_QUEUE_SERIAL);

NSLog(@"开始 - %@",[NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
});
NSLog(@"第一次结束 - %@",[NSThread currentThread]);

// 栅栏函数的作用是讲队列中的任务进行分组,所以我们只要关注任务1、任务2
dispatch_barrier_async(queue, ^{
NSLog(@"----------栅栏任务----------%@",[NSThread currentThread]);
});
NSLog(@"栅栏结束 - %@",[NSThread currentThread]);

dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务2 - %@",[NSThread currentThread]);
});
NSLog(@"第二次结束 - %@",[NSThread currentThread]);
}

// ****打印结果****
2022-02-22 16:27:58.036179+0800 NSThread_Demo[4930:3731173] 开始 - <_NSMainThread: 0x282bfc800>{number = 1, name = main}
2022-02-22 16:27:58.036253+0800 NSThread_Demo[4930:3731173] 第一次结束 - <_NSMainThread: 0x282bfc800>{number = 1, name = main}
2022-02-22 16:27:58.036278+0800 NSThread_Demo[4930:3731173] 栅栏结束 - <_NSMainThread: 0x282bfc800>{number = 1, name = main}
2022-02-22 16:27:58.036303+0800 NSThread_Demo[4930:3731173] 第二次结束 - <_NSMainThread: 0x282bfc800>{number = 1, name = main}
2022-02-22 16:28:00.041655+0800 NSThread_Demo[4930:3731195] 延迟2s的任务1 - <NSThread: 0x282bfaa80>{number = 4, name = (null)}
2022-02-22 16:28:00.042098+0800 NSThread_Demo[4930:3731195] ----------栅栏任务----------<NSThread: 0x282bfaa80>{number = 4, name = (null)}
2022-02-22 16:28:02.047479+0800 NSThread_Demo[4930:3731195] 延迟2s的任务2 - <NSThread: 0x282bfaa80>{number = 4, name = (null)}

// testBarrier2
- (void)testBarrier2{
// 并发队列使用栅栏函数
dispatch_queue_t queue = dispatch_queue_create("ZJ", DISPATCH_QUEUE_CONCURRENT);

NSLog(@"开始 - %@",[NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务1 - %@",[NSThread currentThread]);
});
NSLog(@"第一次结束 - %@",[NSThread currentThread]);

// 由于并发队列异步任务时乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序
dispatch_barrier_async(queue, ^{
NSLog(@"----------栅栏任务----------%@",[NSThread currentThread]);
});
NSLog(@"栅栏结束 - %@",[NSThread currentThread]);

dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务2 - %@",[NSThread currentThread]);
});
NSLog(@"第二次结束 - %@",[NSThread currentThread]);
}

// ****打印结果****
2022-02-22 16:24:07.750567+0800 NSThread_Demo[4927:3729822] 开始 - <_NSMainThread: 0x2809c8800>{number = 1, name = main}
2022-02-22 16:24:07.750635+0800 NSThread_Demo[4927:3729822] 第一次结束 - <_NSMainThread: 0x2809c8800>{number = 1, name = main}
2022-02-22 16:24:07.750665+0800 NSThread_Demo[4927:3729822] 栅栏结束 - <_NSMainThread: 0x2809c8800>{number = 1, name = main}
2022-02-22 16:24:07.750685+0800 NSThread_Demo[4927:3729822] 第二次结束 - <_NSMainThread: 0x2809c8800>{number = 1, name = main}
2022-02-22 16:24:09.756044+0800 NSThread_Demo[4927:3729837] 延迟2s的任务1 - <NSThread: 0x2809840c0>{number = 5, name = (null)}
2022-02-22 16:24:09.756553+0800 NSThread_Demo[4927:3729837] ----------栅栏任务----------<NSThread: 0x2809840c0>{number = 5, name = (null)}
2022-02-22 16:24:11.761825+0800 NSThread_Demo[4927:3729837] 延迟2s的任务2 - <NSThread: 0x2809840c0>{number = 5, name = (null)}

dispatch_semaphore_t

信号量主要作用于同步锁,用于控制 GCD 最大并发数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- (void)testSemaphore{
/*
应用场景:同步锁,控制GCD最大并发数

- dispatch_semphore_create():创建信号量
- dispatch_semphore_wait():等待信号量,信号量减1,当信号量 < 0 时会阻塞当前线程,根据传入的等待时间决定接下来的操作,如果永久等待将等到信号(signal)才能执行下去
- dispatch_semphore_signal():释放信号量,信号量加1,当信号量 >= 0 会执行wait之后的代码
*/
dispatch_queue_t queue = dispatch_queue_create("ZJ", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i ++) {
dispatch_async(queue, ^{
NSLog(@"当前 - %d,线程 - %@",i,[NSThread currentThread]);
});
}

// 利用信号量来改写
dispatch_semaphore_t sem = dispatch_semaphore_create(0);

for (int i = 0; i < 10; i ++) {
dispatch_async(queue, ^{
NSLog(@"当前 - %d 线程 - %@",i,[NSThread currentThread]);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

}

由上面的代码可知,在使用信号量之前的打印结果是无序的:

1
2
3
4
5
6
7
8
9
10
2022-02-23 08:59:56.276005+0800 NSThread_Demo[5019:3790181] 当前 - 1,线程 - <NSThread: 0x2825f0c00>{number = 4, name = (null)}
2022-02-23 08:59:56.276005+0800 NSThread_Demo[5019:3790180] 当前 - 0,线程 - <NSThread: 0x2825f0080>{number = 3, name = (null)}
2022-02-23 08:59:56.276048+0800 NSThread_Demo[5019:3790182] 当前 - 3,线程 - <NSThread: 0x2825e5180>{number = 7, name = (null)}
2022-02-23 08:59:56.276079+0800 NSThread_Demo[5019:3790181] 当前 - 4,线程 - <NSThread: 0x2825f0c00>{number = 4, name = (null)}
2022-02-23 08:59:56.276081+0800 NSThread_Demo[5019:3790184] 当前 - 2,线程 - <NSThread: 0x2825eca40>{number = 6, name = (null)}
2022-02-23 08:59:56.276110+0800 NSThread_Demo[5019:3790181] 当前 - 7,线程 - <NSThread: 0x2825f0c00>{number = 4, name = (null)}
2022-02-23 08:59:56.276093+0800 NSThread_Demo[5019:3790182] 当前 - 6,线程 - <NSThread: 0x2825e5180>{number = 7, name = (null)}
2022-02-23 08:59:56.276157+0800 NSThread_Demo[5019:3790182] 当前 - 9,线程 - <NSThread: 0x2825e5180>{number = 7, name = (null)}
2022-02-23 08:59:56.276172+0800 NSThread_Demo[5019:3790185] 当前 - 5,线程 - <NSThread: 0x2825f0440>{number = 5, name = (null)}
2022-02-23 08:59:56.276185+0800 NSThread_Demo[5019:3790181] 当前 - 8,线程 - <NSThread: 0x2825f0c00>{number = 4, name = (null)}

由上面的代码可知,在使用信号量之后,打印的结果是有序的,如下:

1
2
3
4
5
6
7
8
9
10
2022-02-23 08:59:56.276197+0800 NSThread_Demo[5019:3790186] 当前 - 0 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.276416+0800 NSThread_Demo[5019:3790186] 当前 - 1 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.276572+0800 NSThread_Demo[5019:3790186] 当前 - 2 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.276706+0800 NSThread_Demo[5019:3790186] 当前 - 3 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.276828+0800 NSThread_Demo[5019:3790186] 当前 - 4 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.276934+0800 NSThread_Demo[5019:3790186] 当前 - 5 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.276997+0800 NSThread_Demo[5019:3790186] 当前 - 6 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.277125+0800 NSThread_Demo[5019:3790186] 当前 - 7 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.277179+0800 NSThread_Demo[5019:3790186] 当前 - 8 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}
2022-02-23 08:59:56.277298+0800 NSThread_Demo[5019:3790186] 当前 - 9 线程 - <NSThread: 0x2825f3880>{number = 8, name = (null)}

dispatch_source_t

dispatch_source_t 主要用于计时操作,其原因是因为它创建的timer不依赖于Runloop,且计时精准度比 NSTimer 高,dispatch_source_t 有一点必须要设值为属性,否则会被立即释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
- (void)testSource{
/*
dispatch_source

应用场景:GCDTimer
在iOS开发中一般使用NSTimer来处理定时逻辑,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下
如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就挂机了

dispatch_source是一种基本的数据类型,可以用来监听一些底层的系统事件
- Timer Dispatch Source:定时器事件源,用来生成周期性的通知或回调
- Signal Dispatch Source:监听信号事件源,当有UNIX信号发生时会通知
- Descriptor Dispatch Source:监听文件或socket事件源,当文件或socket数据发生变化时会通知
- Process Dispatch Source:监听进程事件源,与进程相关的事件通知
- Mach port Dispatch Source:监听Mach端口事件源
- Custom Dispatch Source:监听自定义事件源

主要使用的API:
- dispatch_source_create:创建时间源
- dispatch_source_set_event_handler:设值数据源回调
- dispatch_source_merge_data:设值事件源数据
- dispatch_source_get_data:获取事件源数据
- dispatch_resume:继续
- dispatch_suspend:挂起
- dispatch_cancle:取消
*/

// 创建队列
dispatch_queue_t queue = dispatch_queue_create("ZJ", DISPATCH_QUEUE_SERIAL);
// 创建timer
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置timer首次执行事件,间隔,精确度
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), 1.0 * NSEC_PER_SEC, 0);
// 设置timer回调事件
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"GCDTimer");
});
// 默认是挂起状态,需要手动激活
dispatch_resume(self.timer);
}

// 打印结果
2022-02-23 10:01:59.795986+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:00.795906+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:01.795952+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:02.795970+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:03.796053+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:04.796022+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:05.796038+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:06.796074+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:07.796115+0800 NSThread_Demo[5145:3819354] GCDTimer
2022-02-23 10:02:08.796159+0800 NSThread_Demo[5145:3819355] GCDTimer
2022-02-23 10:02:09.796130+0800 NSThread_Demo[5145:3819355] GCDTimer
2022-02-23 10:02:10.796167+0800 NSThread_Demo[5145:3819355] GCDTimer
2022-02-23 10:02:11.796161+0800 NSThread_Demo[5145:3819355] GCDTimer
2022-02-23 10:02:12.796162+0800 NSThread_Demo[5145:3819355] GCDTimer
2022-02-23 10:02:13.796169+0800 NSThread_Demo[5145:3819355] GCDTimer
...

NSOperation

NSOperation 是基于 GCD 之上的更高一层的封装, NSOpetation 需要配合 NSOpetationQueue 来实现多线程

NSOpetation 实现多线程的步骤如下:

  • 创建任务:先将需要执行的操作封装到 NSOperation 对象中。
  • 创建队列:创建 NSOperationQueue
  • 将任务加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 中。
1
2
3
4
5
6
7
8
9
10
11
- (void)testBaseNSOperation{
// 处理事务
NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"ZJ"];
// 创建队列
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
// 操作加入队列
[queue addOperation:op];
}
- (void)handleInvocation:(id)operation{
NSLog(@"%@ - %@",operation,[NSThread currentThread]);
}

需要注意的是,NSOperation 是个抽象类,实际运用时中需要使用它的子类,有三种方式:

  • 使用子类 NSInvocationOperation
1
2
3
4
5
- (void)createNSOperation{
// 创建 NSInvocationOperation 对象并关联方法,之后start
NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"ZJ"];
[invocationOperation start];
}
  • 使用子类 NSBlockOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)testBlockOperationExecution{
// 通过 addExecutionBlock 这个方法可以让 NSBlockOperation 实现多线程
// NSBlockOperation 创建时 block 中的任务是主线程执行,而运用 addExecutionBlock 加入的任务是在子线程执行的
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"main task = >currentThread: %@",[NSThread currentThread]);
}];

[blockOperation addExecutionBlock:^{
NSLog(@"task1 = >currentThread: %@",[NSThread currentThread]);
}];

[blockOperation addExecutionBlock:^{
NSLog(@"task2 = >currentThread: %@",[NSThread currentThread]);
}];

[blockOperation addExecutionBlock:^{
NSLog(@"task3 = >currentThread: %@",[NSThread currentThread]);
}];

[blockOperation start];
}

// 打印结果
2022-02-23 13:48:54.022671+0800 NSThread_Demo[5246:3887949] main task = >currentThread: <_NSMainThread: 0x281598800>{number = 1, name = main}
2022-02-23 13:48:54.022688+0800 NSThread_Demo[5246:3887974] task1 = >currentThread: <NSThread: 0x2815c8180>{number = 4, name = (null)}
2022-02-23 13:48:54.022786+0800 NSThread_Demo[5246:3887971] task2 = >currentThread: <NSThread: 0x2815da480>{number = 5, name = (null)}
2022-02-23 13:48:54.022786+0800 NSThread_Demo[5246:3887976] task3 = >currentThread: <NSThread: 0x2815dc0c0>{number = 6, name = (null)}
  • 定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 自定义继承自NSOperation的子类
@interface ZJOperation : NSOperation
@end

@implementation ZJOperation
- (void)main{
for (int i = 0; i < 3; i ++) {
NSLog(@"NSOperation的子类:%@",[NSThread currentThread]);
}
}
@end

// 使用
- (void)testZJOperation{
// 运用集成自NSOperation的子类,首先我们定义一个集成自NSOperation的类,然后重写它的main方法
ZJOperation * operation = [[ZJOperation alloc] init];
[operation start];
}

// 打印结果
2022-02-25 15:16:22.694568+0800 NSThread_Demo[5696:4241286] NSOperation的子类:<_NSMainThread: 0x282180800>{number = 1, name = main}
2022-02-25 15:16:22.694645+0800 NSThread_Demo[5696:4241286] NSOperation的子类:<_NSMainThread: 0x282180800>{number = 1, name = main}
2022-02-25 15:16:22.694670+0800 NSThread_Demo[5696:4241286] NSOperation的子类:<_NSMainThread: 0x282180800>{number = 1, name = main}

NSOperationQueue

NSOperationQueue添加事务

NSOperationQueue 有两种队列:主队列、其他队列。其他队列包含了 串行和并发。

  • 主队列:主队列 上的任务时在 主线程 执行的

  • 其他队列(非主队列):加入到 非队列 中的任务 默认就是并发,开启多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)testNSOperationQueue{
/*
NSInvocationOperation 和 NSBlockOperation 两者之间的区别在于:
- 前者类似target形式
- 后者类似block形式——函数式编程,业务逻辑代码可读性更高

NSOperationQueue 是异步执行的,所以 任务一、任务二的完成顺序不确定
*/
// 初始化事务
NSBlockOperation * bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1——%@",[NSThread currentThread]);
}];
// 添加事务
[bo addExecutionBlock:^{
NSLog(@"任务2——%@",[NSThread currentThread]);
}];
// 回调监听
bo.completionBlock = ^{
NSLog(@"完成了!!!");
};

NSOperationQueue * queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo];
NSLog(@"事务添加加进了NSOperationQueue");
}

// 打印结果
2022-02-25 15:28:17.704473+0800 NSThread_Demo[5720:4246238] 事务添加加进了NSOperationQueue
2022-02-25 15:28:17.704513+0800 NSThread_Demo[5720:4246260] 任务1——<NSThread: 0x282f6c340>{number = 5, name = (null)}
2022-02-25 15:28:17.704552+0800 NSThread_Demo[5720:4246260] 任务2——<NSThread: 0x282f6c340>{number = 5, name = (null)}
2022-02-25 15:28:17.704602+0800 NSThread_Demo[5720:4246260] 完成了!!!

设值执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)testQueueSequence{
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i ++) {
[queue addOperationWithBlock:^{
NSLog(@"%@ --- %d",[NSThread currentThread],i);
}];
}
}

// 打印结果
2022-03-08 09:11:03.759862+0800 NSThread_Demo[4217:3030339] <NSThread: 0x281b90240>{number = 3, name = (null)} --- 1
2022-03-08 09:11:03.760398+0800 NSThread_Demo[4217:3030384] <NSThread: 0x281be7a80>{number = 7, name = (null)} --- 3
2022-03-08 09:11:03.760526+0800 NSThread_Demo[4217:3030340] <NSThread: 0x281b830c0>{number = 6, name = (null)} --- 2
2022-03-08 09:11:03.760655+0800 NSThread_Demo[4217:3030339] <NSThread: 0x281b90240>{number = 3, name = (null)} --- 4
2022-03-08 09:11:03.760697+0800 NSThread_Demo[4217:3030343] <NSThread: 0x281b84280>{number = 5, name = (null)} --- 0

设置优先级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- (void)testOperationQuality{
/*
NSOperation设值优先级只会让CPU有更高的几率调用,不是说设值高就一定全部先完成
- 不适用sleep —— 高优先级的任务先于低优先级的任务
- 使用sleep进行延时 —— 高优先级的任务慢于低优先级的任务
*/
NSBlockOperation * bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i ++) {
// sleep(1);
NSLog(@"第一个操作 %d --- %@",i,[NSThread currentThread]);
}
}];
// 设置最高优先级
bo1.qualityOfService = NSQualityOfServiceUserInteractive;

NSBlockOperation * bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i ++) {
NSLog(@"第二个操作 %d --- %@",i,[NSThread currentThread]);
}
}];
// 设置最低优先级
bo2.qualityOfService = NSQualityOfServiceBackground;

NSOperationQueue * queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}

// 不使用sleep的打印结果
2022-03-08 09:23:44.072325+0800 NSThread_Demo[4220:3035578] 第一个操作 0 --- <NSThread: 0x2826ac100>{number = 4, name = (null)}
2022-03-08 09:23:44.072420+0800 NSThread_Demo[4220:3035578] 第一个操作 1 --- <NSThread: 0x2826ac100>{number = 4, name = (null)}
2022-03-08 09:23:44.072448+0800 NSThread_Demo[4220:3035578] 第一个操作 2 --- <NSThread: 0x2826ac100>{number = 4, name = (null)}
2022-03-08 09:23:44.072516+0800 NSThread_Demo[4220:3035578] 第一个操作 3 --- <NSThread: 0x2826ac100>{number = 4, name = (null)}
2022-03-08 09:23:44.072447+0800 NSThread_Demo[4220:3035582] 第二个操作 0 --- <NSThread: 0x2826a8300>{number = 5, name = (null)}
2022-03-08 09:23:44.072555+0800 NSThread_Demo[4220:3035578] 第一个操作 4 --- <NSThread: 0x2826ac100>{number = 4, name = (null)}
2022-03-08 09:23:44.073242+0800 NSThread_Demo[4220:3035582] 第二个操作 1 --- <NSThread: 0x2826a8300>{number = 5, name = (null)}
2022-03-08 09:23:44.073345+0800 NSThread_Demo[4220:3035582] 第二个操作 2 --- <NSThread: 0x2826a8300>{number = 5, name = (null)}
2022-03-08 09:23:44.073484+0800 NSThread_Demo[4220:3035582] 第二个操作 3 --- <NSThread: 0x2826a8300>{number = 5, name = (null)}
2022-03-08 09:23:44.073546+0800 NSThread_Demo[4220:3035582] 第二个操作 4 --- <NSThread: 0x2826a8300>{number = 5, name = (null)}

// 使用sleep的打印结果
2022-03-08 09:25:01.471850+0800 NSThread_Demo[4223:3036749] 第二个操作 0 --- <NSThread: 0x282c1de40>{number = 4, name = (null)}
2022-03-08 09:25:01.472242+0800 NSThread_Demo[4223:3036749] 第二个操作 1 --- <NSThread: 0x282c1de40>{number = 4, name = (null)}
2022-03-08 09:25:01.472334+0800 NSThread_Demo[4223:3036749] 第二个操作 2 --- <NSThread: 0x282c1de40>{number = 4, name = (null)}
2022-03-08 09:25:01.472867+0800 NSThread_Demo[4223:3036749] 第二个操作 3 --- <NSThread: 0x282c1de40>{number = 4, name = (null)}
2022-03-08 09:25:01.473057+0800 NSThread_Demo[4223:3036749] 第二个操作 4 --- <NSThread: 0x282c1de40>{number = 4, name = (null)}
2022-03-08 09:25:02.472831+0800 NSThread_Demo[4223:3036750] 第一个操作 0 --- <NSThread: 0x282c4c080>{number = 5, name = (null)}
2022-03-08 09:25:03.474228+0800 NSThread_Demo[4223:3036750] 第一个操作 1 --- <NSThread: 0x282c4c080>{number = 5, name = (null)}
2022-03-08 09:25:04.475736+0800 NSThread_Demo[4223:3036750] 第一个操作 2 --- <NSThread: 0x282c4c080>{number = 5, name = (null)}
2022-03-08 09:25:05.477191+0800 NSThread_Demo[4223:3036750] 第一个操作 3 --- <NSThread: 0x282c4c080>{number = 5, name = (null)}
2022-03-08 09:25:06.478576+0800 NSThread_Demo[4223:3036750] 第一个操作 4 --- <NSThread: 0x282c4c080>{number = 5, name = (null)}

设置并发数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)testOperationMaxCount{
/*
在GCD中只能使用信号量来设置并发数
而NSOperation轻易就能设置并发数
通过设置maxConcurrentOperationCount来控制单次队列去执行的任务数
*/
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
queue.maxConcurrentOperationCount = 2;

for (int i = 0; i < 5; i ++) {
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%d -- %@",i,[NSThread currentThread]);
}];
}
}

// 查看打印结果
2022-03-08 11:12:55.459322+0800 NSThread_Demo[4293:3074227] 0 -- <NSThread: 0x283260080>{number = 4, name = (null)}
2022-03-08 11:12:55.459326+0800 NSThread_Demo[4293:3074223] 1 -- <NSThread: 0x283264140>{number = 3, name = (null)}
2022-03-08 11:12:57.463085+0800 NSThread_Demo[4293:3074223] 3 -- <NSThread: 0x283264140>{number = 3, name = (null)}
2022-03-08 11:12:57.464903+0800 NSThread_Demo[4293:3074227] 2 -- <NSThread: 0x283260080>{number = 4, name = (null)}
2022-03-08 11:12:59.468845+0800 NSThread_Demo[4293:3074225] 4 -- <NSThread: 0x2832724c0>{number = 6, name = (null)}

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (void)testOperationDependency{
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
NSBlockOperation * bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"请求token");
}];

NSBlockOperation * bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着token,请求数据1");
}];

NSBlockOperation * bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着数据1,请求数据2");
}];

[bo2 addDependency:bo1];
[bo3 addDependency:bo2];

[queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];

NSLog(@"执行完成了?我要干其他事情了");
}

// 查看打印结果
2022-03-08 11:17:14.425701+0800 NSThread_Demo[4295:3076256] 请求token
2022-03-08 11:17:14.931254+0800 NSThread_Demo[4295:3076255] 拿着token,请求数据1
2022-03-08 11:17:15.436695+0800 NSThread_Demo[4295:3076258] 拿着数据1,请求数据2
2022-03-08 11:17:15.437079+0800 NSThread_Demo[4295:3076238] 执行完成了?我要干其他事情了

线程间通讯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)testOperationNoti{
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
[queue addOperationWithBlock:^{
NSLog(@"请求网络%@ -- %@",[NSOperationQueue currentQueue],[NSThread currentThread]);

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"刷新UI%@ -- %@",[NSOperationQueue currentQueue],[NSThread currentThread]);
}];
}];
}

// 查看打印结果
2022-03-08 11:20:27.970126+0800 NSThread_Demo[4297:3077823] 请求网络<NSOperationQueue: 0x10bd087d0>{name = 'Felix'} -- <NSThread: 0x28206b140>{number = 4, name = (null)}
2022-03-08 11:20:27.976293+0800 NSThread_Demo[4297:3077803] 刷新UI<NSOperationQueue: 0x10d005570>{name = 'NSOperationQueue Main Queue'} -- <_NSMainThread: 0x28206c880>{number = 1, name = main}
  • Post title:OC底层原理27:GCD之 NSThread & GCD & NSOperation
  • Post author:张建
  • Create time:2021-03-23 14:59:47
  • Post link:https://redefine.ohevan.com/2021/03/23/OC底层原理/OC底层原理27:GCD之 NSThread & GCD & NSOperation/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.