RunLoop

作者: 月下独酌灬 | 来源:发表于2016-04-28 13:11 被阅读81次

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__);
}

时间循环图解按钮点击

图解按钮点击.png

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");
}

RunLoop.jpg

相关文章

网友评论

      本文标题:RunLoop

      本文链接:https://www.haomeiwen.com/subject/yihhrttx.html