2022iOS面试题之Runloop
1、 问题:什么是RunLoop?
答:RunLoop是通过内部维护
的事件循环来对事件/消息进行管理
的一个对象。
1、没有消息需要处理时,休眠以避免资源占有。
2、有消息需要处理时,立刻被唤醒
————————————————————
问题:main函数为什么能保证一直运行状态不退出?
答:
- 在main函数中调用UIApplicationMain()函数, 这个函数内部会启动一个主线程的RunLoop
- RunLoop是对事件循环的维护机制, 可以不断的接收消息,比如说点击屏幕的事件,滑动列表,及处理网络请求的返回,那么接收消息之后对这个事件进行处理,处理完之后就会再进行等待, 通过用户态到内核态的切换, 从而避免资源占用, 让当前线程处于休眠状态.
- 注意:等待 != 死循环 ,RunLoop的循环通过状态切换, 达到没有消息时休眠,用户态切换到内核态,有消息时唤醒,内核态切换到用户态的这样一个事件循环机制
什么是事件循环,事件循环的机制是怎样的?
- 维护的事件循环可以用来不断的处理消息或者说事件,对他们进行管理
- 同时当没有消息需要管理时用从用户态切换到内核态,由此可以用来进行当前线程的休眠,然后避免资源占用
- 同时当有消息需要处理时,会发生从内核态到用户态的切换,然后当前的用户线程会被唤醒
- 所以状态的切换才是 RunLoop 的关键点
————————————————————
在 OC 中实际提供了两个 RunLoop 的。
一个是 NSRunLoop,一个是 CFRunLoop。
NSRunLoop 是对 CFRunLoop 的封装,提供了一些面向对象的 API。
NSRunLoop 是位于 Foundation 当中的,CFRunLoop 位于 CoreFoundation 当中的。
RunLoop 的数据结构主要有三个:
* CFRunLoop * CFRunLoopMode * Source/Timer/Observer
NSRunLoop是对CFRunLoop的封装:所以分析CFRunLoop的数据结构就得到NSRunLoop的数据结构,
* 一个runLoop可以有多个mode, 一对多关系
* mode 和Source, Timer, Observer 是一对多的关系
可以看到, 一个RunLoop可以有多个Mode, 而每个Mode中又可以存放多个不同的事件, 我们在切换Mode时, 其他Mode的事件将不会被响应.
面试题:滑动的tableview定时器不执行?多个timer怎么添加到mode上?
model与model之间是相互隔离的,当runloop切换到model1的时候,model的timer就不会执行,就产生了滑动的tableview定时器不执行的问题,滑动之后,runloop的model由kCFRunLoopDefaultModel切换到UITrackingRunLoopDefaultModel,
解决方法:使用方法CFRunLoopAddTimer(runloop,timer,commonMode)方法,将NSTimer添加到runloop的commonMode中,commonMode不是一个实际存在的mode,它只是把一下mode打上commonMode的标记,把某个事件同步到多个mode中,这个列子就是把NSTimer添加到当前的mode里,让它继续执行。
————————————————————————————
问题:CommonModes是否使用过, 你对CommonModes事怎样理解?
- CommonModes
不是实际存在
的一种Mode - 是同步Source/Timer/Observer到多个Mode中的
一种技术方案
在 OC 当中经常会通过 NSRunLoopCommonModes 字符串常量来表达 CommonMode。
- CommonModes实现:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
————————————————————————————
Source0和Source1的区别:
(source0:需要手动唤醒线程。source1:具备唤醒线程的能力)
source0: 非系统事件.
只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
source1 :系统事件
包含了一个 mach_port和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程
————————————————————————————
————————————————————————————
1、怎样实现一个常驻线程?
(1)、为当前线程开启一个RunLoop。
(2)、向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环。
(3)、启动该RunLoop。
实现一个常驻线程的基本步骤:
* 1)为当前线程开启一个 RunLoop :NSRunLoop *runLoop = [NSRunLoop currentRunLoop];,因为获取当前 RunLoop 这个方法本身会查找如果当前线程没有 RunLoop 的话,会在系统的内部创建
* 2)如果线程没有资源或者事件源要处理的话,默认情况下是不能维持事件循环的就会直接退出了,所以需要给他添加一个 Port/Source 来维持他的时间循环机制
* 3)然后再调用 RunLoop 的 run 方法就可以实现一个常驻线程
————————————————————————————
问题:怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作?
答:
* 用户进行滑动过程中,当前RunLoop运行在UITrackingRunLoopMode下
* 我们一般在子线程中进行网络请求, 所以可以将子线程抛给主线程数据并进行UI更新的逻辑封装起来提交到主线程的NSDefaultRunLoopMode下.
* 这样抛回来的任务,当用户滑动时处于UITrackingRunLoopMode下就不会执行任务. 当手停止滑动操作后, 当前线程mode切换到NSDefaultRunLoopMode下,再处理子线程上抛给主线程的任务,这样就不会打断用户滑动操作.
问题:什么时候使用Runloop?
当需要和该线程进行交互的时候才会使用Runloop
问题: RunLoop 有哪些应用?
答: 常驻内存、AutoreleasePool 自动释放池
问题: Runloop和线程是什么关系?
答:
* 每条线程都有唯一的一个与之对应的RunLoop对象,其关系是保存在一个全局的 Dictionary 里;
* 主线程的RunLoop已经自动创建,子线程的RunLoop需要手动创建;
* RunLoop在第一次获取时创建,在线程结束时销毁
问题: AutoreleasePool 和 RunLoop 有什么联系?
答:
iOS应用启动后会注册两个 Observer 管理和维护 AutoreleasePool。应用程序刚刚启动时默认注册了很多个Observer,其中有两个Observer的 callout 都是 _ wrapRunLoopWithAutoreleasePoolHandler,这两个是和自动释放池相关的两个监听。
* 第一个 Observer 会监听 RunLoop 的进入,它会回调objc_autoreleasePoolPush() 向当前的 AutoreleasePoolPage 增加一个哨兵对象标志创建自动释放池。这个 Observer 的 order 是 -2147483647 优先级最高,确保发生在所有回调操作之前。
* 第二个 Observer 会监听 RunLoop 的进入休眠和即将退出 RunLoop 两种状态,在即将进入休眠时会调用 objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 根据情况从最新加入的对象一直往前清理直到遇到哨兵对象。而在即将退出 RunLoop 时会调用objc_autoreleasePoolPop() 释放自动自动释放池内对象。这个Observer 的 order 是 2147483647 ,优先级最低,确保发生在所有回调操作之后。
问题:NSRunLoop 和 CFRunLoopRef 区别?
答: CFRunLoopRef 基于C 线程安全,NSRunLoop 基于 CFRunLoopRef 面向对象的API 是不安全的