RunLoop
作用
- 使程序一直运行并接收用户的输入
- 决定程序在何时处理哪些事件
- 调用解耦(Message Queue)
- 节省CPU时间(当程序启动后,什么都没有执行的话,就不用让CPU来消耗资源来执行,直接进入睡眠状态)
模式
RunLoop
在同一段时间只能且必须在一种特定的模式下运行如果要更换 Mode,必须先停止当前的 Loop,然后再重新启动 Loop
Mode 是保证滚动流畅的关键
NSDefaultRunLoopMode
:默认状态、空闲状态UITrackingRunLoopMode
:滚动模式,专门为滚动视图设计的NSRunLoopCommonModes
:默认包含以上两种模式UIInitializationRunLoopMode
:私有的,App启动时
模拟 RunLoop 实现
- 准备的方法
void callFunc(int num) {
NSLog(@"正在执行 %d 功能...",num);
}
- main 方法中的实现
int main(int argc, const char * argv[]) {
@autoreleasepool {
int result = 0;
while (YES) {
printf("请输入选项,O表示退出");
// 阻塞式 : 需要控制台输入值才能继续运行
scanf("%d",&result);
if (0==result) {
NSLog(@"程序正常退出");
break;
} else {
callFunc(result);
}
}
}
return 0;
}
运行循环与时钟
- 实际开发中,不建议将时钟的运行模式设置成 NSRunLoopCommonModes ,一旦耗时操作会影响流畅度
- (void)viewDidLoad {
[super viewDidLoad];
// 定时器 : 添加了滚动事件之后,定时器就不再执行了
// [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
// timerWithTimeInterval 创建时钟时,需要将定时器添加到运行循环中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
时钟的执行方法入口
- 模拟耗时操作 : 此处不能使用sleep, 因为线程休眠的时候事件循环不响应任何事件,开发中一定不要使用
- (void)fire
{
// 不能使用 sleep
// [NSThread sleepForTimeInterval:1.0];
// 耗时操作
for (int i = 0; i < 1000*1000; i++) {
NSString *str = [NSString stringWithFormat:@"hello %d",i];
}
static int num = 0;
num++;
NSLog(@"%d",num);
}
运行测试,会发现卡顿非常严重
将时钟添加到子线程中工作
/// 线程
@property (nonatomic,strong) NSThread *thread;
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
[self.thread start];
}
- 注意 : 主线程的运行循环是默认启动的,但是子线程的运行循环是默认不工作的,这样能够保证线程执行完毕后,自动被销毁
- (void)demo
{
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 启动当前线程的运行循环;当前线程的运行循环是个不同于while的死循环
// [[NSRunLoop currentRunLoop] run];
CFRunLoopRun();
NSLog(@"over");
}
- 停止运行循环
// 停止当前运行循环
CFRunLoopStop(CFRunLoopGetCurrent());
按钮点击
创建按钮
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *addBtn = [UIButton buttonWithType:UIButtonTypeContactAdd];
[self.view addSubview:addBtn];
addBtn.center = self.view.center;
// 向 runloop 注册监听,点击按钮,执行 ViewController 的 click 方法
[addBtn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
}
点击方法
- (void)click
{
NSLog(@"%s",__FUNCTION__);
}
时间循环图解按钮点击
RunLoop-主线程
- 主线程的消息循环是默认开启.
- 在主线程中使用
定时源
.即定时器
. - 步骤 : 将
定时源
添加到当前线程
的消息循环.
代码实现
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self timerDemo];
}
- (void)timerDemo
{
// 创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
// 将定时器添加到消息循环
// currentRunLoop : 获取到当前的消息循环
// forMode : 当前定时源timer的运行模式
// NSRunLoopCommonModes : 模式组,里面包含了几种运行模式,kCFRunLoopDefaultMode / UITrackingRunLoopMode
// 消息循环也是运行在一个模式下面的,默认的模式是kCFRunLoopDefaultMode,只有定时源的运行模式和消息循环的运行模式保持一致,定时源对应的方法才能执行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)fire
{
NSLog(@"hello %@",[NSRunLoop currentRunLoop].currentMode);
}
RunLoop-子线程
- 子线程的消息循环是默认不开启.
- 在子线程中使用
定时源
.即定时器
.需要我们手动开启子线程的消息循环
. - 步骤 : 将
定时源
添加到当前线程
的消息循环.
代码实现
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// [self timerDemo];
NSLog(@"start");
[self performSelectorInBackground:@selector(timerDemo) withObject:nil];
}
- (void)timerDemo
{
//创建定时器(定时源)
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
// 获取当前的消息循环
// currentRunLoop : 当前子线程的消息循环
// 子线程的消息循环默认是不开启的,需要我们手动开启
// 子线程中的运行模式和主线程是一样的
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 手动开启子线程的消息循环
// run : 消息循环不停止的话,后面的代码永远不会被执行的
// runUntilDate : 消息循环运行到指定的日期之后就自动的停止
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
// 提问 : over能够打印出来吗?
NSLog(@"over");
}
- (void)fire
{
NSLog(@"hello");
}