iOS面试剖析runloop

RunLoop知识点

2019-10-08  本文已影响0人  huoshe2019

一、概念

RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象

问题1:什么是事件循环

解释:

备注:
内核态:在一个进程中,如果有系统调用,此时进程处于内核态;系统操作包括:执行文件操作,网络数据发送等操作,此时特权级别比较高,0级。

用户态:当一个进程执行用户自己的代码时,处于用户态,此时特权级别比较低,3级。

小结:
所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如执行文件操作,网络数据发送等操作。
而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作。

以下是操作系统内存空间分布图:

操作系统分布图

问题2:为什么main函数能够保证不退出

解释:
1、在main函数当中,调用了UIApplicationMain函数。
2、UIApplicationMain函数会启动主线程的runloop。
3、runloop是一个通过事件循环处理事件/消息的对象,可以做到“有事做的时候去做事,没事做的时候从用户态切换到内核态,避免资源的占用,当前线程是处于一个休眠状态”。

//main函数代码
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

二、runloop数据结构相关

2.1、runloop相关框架

图3 runloop所在框架

NSRunLoop是CFRunLoop的封装,提供了面向对象的API。

2.2、runloop主要结构

runloop相关的数据结构:

2.2.1、CFRunLoop

主要包含5个部分

问题3:RunLoop和线程之间的关系

解释:
1、线程和RunLoop之间的关系是一一对应的。
2、主线程的RunLoop是默认开启的,子线程的RunLoop是默认不开启的。
3、子线程的RunLoop在你主动获取的情况下,才会创建。
子线程的RunLoop在线程结束时,才会销毁
主线程除外。

问题4:commonModes的作用

解释:

2.2.2、CFRunLoopMode

主要包含以下部分:

2.2.3、CFRunLoopSource

问题5:source0和source1有什么样的区别

解释
source0需要手动唤醒线程。
source1具备唤醒线程的能力。

2.2.3、CFRunLoopTimer

基于事件的定时器
和NSTimer是toll-free bridged (免费桥转换)。

2.2.4、CFRunLoopObserver

观测时间点(共6中状态)

2.2.5、各个数据结构之间的关系

图4 RunLoop结构关系

问题6 RunLoop、Model、Source/Timer/Observer关系

解释
RunLoop和Model是一对多的关系(从CFRunLoop的数据结构modes)。
Model和Source/Timer/Observer是一对多的关系。

问题7 RunLoop为什么有多个Model

解释
这样设计的原因是为了起到事件屏蔽的效果

图5 Model
当RunLoop运行在Mode1上时,只能接收处理Mode1上的source1、observers、timers事件回调,不能接收其它Mode上的source/observer/timer事件回调;起到了事件屏蔽的效果

2.2.6、CommonMode的特殊性

在ios上对应的是NSRunLoopCommonModes。

三、事件循环机制(内部逻辑)

3.1、整体流程

图6 事件循环

1、在RunLoop启动后,会发送一个通知,告知Observer观察者。
2、将要处理Timer/Source0事件,会发送一个通知,告知Observer观察者。
3、处理Source0事件。
4、如果有Source1要处理,会跳过当前流程,到第8步。
5、没有Source1要处理,线程将要休眠,发送通知。
6、休眠,等待唤醒。
7、线程刚被唤醒。
8、处理唤醒时收到的消息。

问题8 当一个处于休眠状态RunLoop通过哪些事件唤醒它

解释

小结:

点击App图标,从程序启动、运行、退出这个过程讲解,系统都发生了什么?

解释
1、程序启动后,调用main函数后,会调用UIApplicationmain,这UIApplicationmain函数内部会启动主线程的RunLoop。经过一系列处理,最终主线程RunLoop处于休眠状态。
2、此时,点击一个屏幕,会产生一个mach_port,基于mach_port最终会转换成一个Source1,然后可以唤醒主线程,运行处理事件。
3、当把程序杀死时候,就会发生退出RunLoop,发送通知即将退出RunLoop,RunLoop退出后,线程也就销毁掉了。

3.2、RunLoop核心

图7 RunLoop核心

1、在main函数中,经过一系列处理,会调用系统函数mach_msg(),就发生了系统调用,这样会从用户态到核心态
2、在核心态下面,在一定条件下(Source1/Timer/外部手动唤醒),mach_msg(),会返回给调用方,也就是程序从核心态到用户态。

四、RunLoop与NSTimer

问题9 滑动TableView的时候我们的定时器还会生效吗?

不会生效
原因:
1、当我们滑动scrollView时,主线程的RunLoop 会切换到UITrackingRunLoopMode这个Mode,执行的也是UITrackingRunLoopMode下的任务(Mode中的Source/Timer/Observer)。
2、而timer 是添加在NSDefaultRunLoopMode下的,所以timer任务并不会执行,只有当UITrackingRunLoopMode的任务执行完毕,runloop切换到NSDefaultRunLoopMode后,才会继续执行timer。

解决问题:
我们只需要在添加timer 时,将mode 设置为NSRunLoopCommonModes即可。

五、RunLoop与多线程

线程和RunLoop是一一对应的
自己创建的线程默认没有RunLoop的

问题10 怎么实现一个常驻线程

//创建线程
    HLThread *subThread = [[HLThread alloc] initWithTarget:self selector:@selector(subThreadEntryPoint) object:nil];
    [subThread setName:@"HLThread"];
    [subThread start];
//开启RunLoop
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    //如果注释了下面这一行,子线程中的任务并不能正常执行
    [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
    [runLoop run];

六、面试问题总结

问题11、什么是RunLoop,它是怎样做到有事做事,没事休息的?

1、RunLoop是一个通过内部循环对事件/消息进行管理的一个对象
2、程序运行会调用main函数,在main函数里面调用UIApplicationMain,UIApplicationMain函数会启动主线程的runloop。
3、runloop运行后,会调用系统方法mach_msg(),会使得程序从用户态变成核心态,此时线程处于休眠状态。
4、当有外界条件变化(Source/Timer/Observer),mach_msg会使得程序从核心态变成用户态,此时线程处于活跃状态。

问题12、RunLoop与线程是怎么样的关系

1、RunLoop与线程是一一对应的关系。
2、一个线程默认是没有runloop的(主线程除外)。

问题13、如何实现一个常驻线程

1、为当前线程开启一个RunLoop
2、向RunLoop中添加一个Port/Source等维持RunLoop的事件循环
3、启动该RunLoop

问题14、怎样保证子线程数据回来更新UI的时候,不打断用户的滑动操作?

1、把子线程抛给主线程进行UI更新的逻辑,可以包装起来,提交到主线程的NSDefaultRunLoopMode模式下面。
2、因为用户滑动操作是在UITrackingRunLoopMode模式下进行的。

//参考代码事件
[self.tableView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
上一篇下一篇

猜你喜欢

热点阅读