OC学习38.2:Runloop线程保活

张建 lol

创建线程类

.h 文件下:

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
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZJThread : NSThread

@end

NS_ASSUME_NONNULL_END

//
// ZJThread.m
// Runloop-线程保活
//
// Created by Mac on 2023/2/16.
//

#import "ZJThread.h"

@implementation ZJThread
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end

.m 文件下:

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
#import "NextViewController.h"

@interface NextViewController ()
@end

@implementation NextViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// NSThread 频繁创建线程
ZJThread * thread = [[ZJThread alloc]initWithTarget:self selector:@selector(test) object:nil];
[thread start];
}

// 子线程需要执行的任务
- (void)test{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (void)dealloc{
NSLog(@"%s",__func__);
}

@end

Run 起来之后

我们发现每次点击,都会去执行任务: ZJThread 频繁创建线程

1
2
3
4
2023-02-16 19:06:19.752652+0800 Runloop-频繁创建线程[16937:1026536] -[NextViewController test] <ZJThread: 0x28150ce80>{number = 6, name = (null)}
2023-02-16 19:06:19.753833+0800 Runloop-频繁创建线程[16937:1026536] -[ZJThread dealloc]
2023-02-16 19:06:20.309051+0800 Runloop-频繁创建线程[16937:1026537] -[NextViewController test] <ZJThread: 0x28152ca00>{number = 7, name = (null)}
2023-02-16 19:06:20.309697+0800 Runloop-频繁创建线程[16937:1026537] -[ZJThread dealloc]

可以看到任务一完成,线程就释放,并且这样 频繁的创建线程,很消耗资源

我们可以用 Runloop 来延长线程的生命周期,不让线程挂掉

运行一下:

1
2
3
4
5
6
7
8
// 子线程需要执行的任务
- (void)test{
NSLog(@"%s %@", __func__, [NSThread currentThread]);

[[NSRunLoop currentRunLoop] run];

NSLog(@"%s ----end----", __func__);
}

结果:发现线程执行完成任务还是会结束线程 [ZJThread dealloc],这是因为 run 方法底层调用的是 - (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate; 方法,这个方法会把 runloop 加入到 NSDefaultRunLoopMode 模式下,而 Model 下没有任何 Source0,Source1,Timer,ObserverRunloop 会立马退出,所以我们需要往 Runloop 中添加任务

往 runloop 中添加任务,任何任务都行

1
2
3
4
5
6
7
8
9
10
// 子线程需要执行的任务
- (void)test{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
NSLog(@"🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅");
// 往RunLoop里面添加 Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];

NSLog(@"%s ----end----", __func__);
}

Run:

1
2
2023-02-16 19:15:08.021488+0800 Runloop-频繁创建线程[16991:1030153] -[NextViewController test] <ZJThread: 0x281b95a40>{number = 22, name = (null)}
2023-02-16 19:15:08.021835+0800 Runloop-频繁创建线程[16991:1030153] 🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅

在运行线程就不会执行完任务就挂掉,而是执行完任务就休眠

每点击一次屏幕,都是创建了 ZJThread * thread = [[ZJThread alloc]initWithTarget:self selector:@selector(test) object:nil]; 经过 [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];[[NSRunLoop currentRunLoop] run]; 线程进入休眠,导致我们没办法唤醒线程和执行线程任务,继续修改

把 thread 改成局部变量

1
self.thread = [[ZJThread alloc] initWithTarget:self selector:@selector(run) object:nil];

上面的写法:self 会引用 threadthread 会引用 self,造成循环引用

用block实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad {
[super viewDidLoad];

// 如果使用如下方式创建thread,self会引用thread,thread会引用self,会造成循环引用。
// [[ZJThread alloc] initWithTarget:self selector:@selector(run) object:nil];

self.thread = [[ZJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);

// 往RunLoop里面添加 Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

// 线程会一直阻塞这这一行,永远不会销毁
[[NSRunLoop currentRunLoop] run];

// 当把NSRunLoop停掉之后,代码就会从下一行往下走,这时候任务执行完成,线程该死的时候就会死了。
NSLog(@"%@----end----", [NSThread currentThread]);
}];
[self.thread start];
}

run 方法:Runloop 无法停止,它专门用于开启一个永不销毁的线程

替换 run

1
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

Runloop 的启动和退出

启动:

  • run:无条件
  • runUntilDate:设定时间限制
  • runMode:beforeDate:在特定模式下

停止:

  • CFRunLoopStop(CFRunLoopGetCurrent())

实现线程保活

ZJThread.h 中:

1
2
3
4
5
6
7
#import "ZJThread.h"

@implementation ZJThread
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end

NextVC 中:

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
85
86
87
88
89
90
91
92
93
94
95
#import "NextViewController.h"
#import "ZJThread.h"

@interface NextViewController ()
@property (nonatomic,strong)ZJThread * thread;
@property (nonatomic,assign)BOOL isStop;
@property (nonatomic,strong)UIButton * stopBtn;

@end

@implementation NextViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// 停止按钮
[self.view addSubview:self.stopBtn];
/*
创建线程有两种方式:
target:会造成循环引用
block:不会造成循环引用
*/
[self blockCreatThread];
}
- (void)blockCreatThread{
__weak typeof(self) weakSelf = self;

self.isStop = NO; // 未停止
self.thread = [[ZJThread alloc] initWithBlock:^{
NSLog(@"%@---begin---",[NSThread currentThread]);

// 往runloop中添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

NSLog(@"%@---end---", [NSThread currentThread]);

/*
run方法无法停止,它专门用于开启一个永不销毁的线程
[[NSRunLoop currentRunLoop] run];
*/
}];
[self.thread start];
}

// 点击
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程要执行的任务
- (void)test{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
NSLog(@"🏌🏌🏌🏌🏌🏌🏌🏀🏀🏀🏅🏅🏅");
}

// 停止
- (void)stop{
if (!self.thread) return;

// 停止子线程
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];

}
- (void)stopThread{
// 标记停止
self.isStop = YES;

// 停止
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;

NSLog(@"%@---end---", [NSThread currentThread]);
}

- (void)dealloc{
NSLog(@"%s",__func__);
// 停止子线程
[self stop];
}

//
- (UIButton *)stopBtn{
if (!_stopBtn) {
_stopBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
_stopBtn.backgroundColor = [UIColor greenColor];
[_stopBtn setTitle:@"stop" forState:UIControlStateNormal];
[_stopBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_stopBtn addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside];
}
return _stopBtn;
}

@end
  • Post title:OC学习38.2:Runloop线程保活
  • Post author:张建
  • Create time:2023-02-16 17:17:17
  • Post link:https://redefine.ohevan.com/2023/02/16/OC/OC学习38.2:Runloop线程保活/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.