1:什么是RunLoop
RunLoop是一种循环机制,当有任务处理时,线程的RunLoop会保持忙碌,而在没有任何任务处理时,会让线程休眠,从而让出CPU。当再次有任务需要处理时,RunLoop会被唤醒,来处理事件,直到任务处理完毕,再次进入休眠。
RunLoop是怎么实现休眠/唤醒机制的:
RunLoop的底层实现基于Mach内核通讯实现的。在Mach中,所有的组件都是一个对象。进程、线程、虚拟内存都是对象。Mach对象之间不能直接调用,对象间的通讯是通过消息机制实现的。
Mach 的 IPC 的核心就是消息(message)在两个端口(port)之间传递。
消息实质上是一个二进制数据包,在头部定义了当前端口和目标端口。

消息的发送接收通过mach_msg()函数实现,mach_msg()函数内部会通过调用mach_msg_trap()函数将现在的用户态切换到内核态。进入到内核态后,会调用Mach内核的mach_msg()函数完成消息传递工作,注意此mach_msg和第一个mach_msg虽然函数名相同,但是实现和意义是完全不同的。

RunLoop的核心机制就是通过调用 mach_msg 等待接受 mach_port 的消息。线程进入休眠, 直到被下面某一个事件唤醒。事件通过mach_port 发送 mach_msg 唤醒RunLoop。

RunLoopModel的类型:
指定事件在运行循环中的优先级的,线程的运行需要不同的模式,去响应各种不同的事件,去处理不同情境模式
Default mode 是RunLoop默认的mode。当RunLoop被创建时,就会对应创建出一个default mode。其余的mode,则是懒加载的。
Event tracking mode 是Cocoa在处理密集传入的事件时所使用的mode(如scrollview的滑动)。
Common modes 其实不算是一个mode,而是一个mode的集合。在Cocoa程序中,默认会包含default,modal,event tracking mode。而在Core Foundation程序中,默认仅有Default mode。我们也可以将自定义的mode加入到common modes中。如果我们希望将事件能够在多个mode下得到处理,则直接将事件注册到Common modes中即可。
事件源:
source事件:触摸事件、UI刷新事件、Selector事件、timer事件:

2:RunLoop与线程之间的关系
在iOS中,除了系统会为主线程自动创建一个RunLoop,在子线程中,我们需要手动获取线程对应的RunLoop:
RunLoop和Thread是一一对应的(key: pthread value:runLoop)
Thread默认是没有对应的RunLoop的,仅当主动调用Get方法时,才会创建
所有Thread线程对应的RunLoop被存储在全局的__CFRunLoops字典中。同时,主线程在static CFRunLoopRef __main,子线程在TSD中,也存储了线程对应的RunLoop,用于快速查找。
获取对应的CFRunLoopRef类,来达到线程安全的目的。

3:如何实现一个RunLoop

- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建子线程并开启
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil];
    self.thread = thread;
    [thread start];
}
-(void)show
{
    // 注意:打印方法一定要在RunLoop创建开始运行之前,如果在RunLoop跑起来之后打印,RunLoop先运行起来,已经在跑圈了就出不来了,进入死循环也就无法执行后面的操作了。
    // 但是此时点击Button还是有操作的,因为Button是在RunLoop跑起来之后加入到子线程的,当Button加入到子线程RunLoop就会跑起来
    NSLog(@"%s",__func__);
    // 1.创建子线程相关的RunLoop,在子线程中创建即可,并且RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入
    // 添加Source [NSMachPort port] 添加一个端口
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    // 添加一个Timer
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//    创建监听者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要处理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要处理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒来了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;

            default:
                break;
        }
    });
//    // 给RunLoop添加监听者
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 2.子线程需要开启RunLoop
//    [[NSRunLoop currentRunLoop]run];
    CFRunLoopRun();
//    CFRelease(observer);
}

-(void)test
{
    static int i = 0;
    i++;
    if (i >=3) {
        CFRunLoopStop(CFRunLoopGetCurrent());
    }
}

4:RunLoop有哪些应用场景
1:事件响应
2:手势识别
3:界面更新
4:定时器
5:自动释放池的创建和释放,销毁的时机如下所示
kCFRunLoopEntry; // 进入runloop之前,创建一个自动释放池
kCFRunLoopBeforeWaiting; // 休眠之前,销毁自动释放池,创建一个新的自动释放池
kCFRunLoopExit; // 退出runloop之前,销毁自动释放池

面试考察点
为什么引入Runloop机制,有什么作用或者好处?
引入Runloop机制的目的是利用RunLoop机制的特点实现整体省电的效果,并且让系统和应用可以流畅的运行,提高响应速度,达到极致的用户体验。

为什么省电?
主要有两点:一、因为不做任何操作的时候主线程Runloop会处于退出状态,不会执行任何空转逻辑,不执行代码自然不消耗CPU资源,自然省电。二、Runloop提供一种班车机制,限制如页面刷新等任务的执行频率,一次Runloop只执行一次,防止多次重复执行代码带来的性能损耗。

为什么可以流程运行?
一个app流畅与否的决定性因素是主线程的阻塞率,在iOS系统中runloop每秒执行60次,理论上主线程runloop达到55帧以上的刷新频率用户就感觉不到卡顿。

Mode机制,同一时间只执行一个Mode内的Source或者Timer,比如拖动的时候只指定拖动Mode,其他Mode 如Default Mode中的源不会被执行,确保了高速滑动的时候不会有其他逻辑阻碍主线程刷新。

Runloop做的是管理视图刷新频率,防止重复运算。由于视图更新必须在主线程,视图的重布局和重绘都会占用主线程的执行时间,一次Runloop循环只执行一次可以最大限度的防止重复运算导致的计算浪费。

管理核心动画。核心动画有三个树,其中render tree 是私有的,应用开发无法访问到。render tree在专用的render server 进程中执行,是真正用来渲染动画的地方,线程优先级高于主线程。所以即使app主线程阻塞,也不会影响到动画的绘制工作。既节省了主线程的计算资源,又使动画可以流畅的执行。

支持异步方法调用,将耗时操作分发到子线程中进行。RunLoop是performSelector的基础设施。我们使用 performSelector:onThread: 或者 performSelecter:afterDelay: 时,实际上系统会创建一个Timer并添加到当前线程的RunLoop中。

还有其他的点,这里不展开,详情可阅读下文应用实践。

当然Runloop不是万能的,如果代码质量差,在一次Runloop循环中执行的时间过久一样会导致卡顿,所以解决卡顿问题也是程序员能力的体现。

如何提高响应速度?
当发生系统事件时,如触碰事件,系统通过Mach Port 发送 Mach消息主动唤醒Runloop。Mach是抢占式操作系统内核,Mach系统IPC机制就是依靠消息机制实现的,所以效率非常高。

https://www.jianshu.com/p/6e62f0492a5f