RunLoop 学习起来是很抽象,也不好理解,所以一定多看几次,多学学才能学好!这也是中高级 iOS 必须掌握的知识点,面试中经常遇到
什么是 RunLoop?
Run 表示运行,Loop 表示循环。结合在一起就是运行循环的意思。RunLoop 就是在程序运行过程中循环做一些事情.
RunLoop 的应用范畴有哪些?
定时器 (Timer)、PerformSelector
GCD Async Main Queue
事件响应、手势识别、界面刷新
网络请求
AutoreieasePool
上面这些底层都是 RunLoop 在支撑,说白了,如果没有 RunLoop 支撑,上面的这些都无法实现。
如果没有 RunLoop 会发生什么呢?像我们的命令行项目,创建出来默认就是没有 RunLoop,请看下图
因为没有 RunLoop,程序执行到第 13 行的时候,就会自动退出.
而我们 iOS 项目的 main 函数里面都有 UIApplicationMain(argc, argv, nil, appDelegateClassName);这个代码,这里就是创建了一个主线程的 RunLoop,所以我们程序不会退出,一直在运行中。我们可以大致写一下 main 函数里面的伪代码如下:
retVal 这个等于 0,当没有事件处理的时候,RunLoop 就会 sleep 就是类似睡觉,一旦有事件需要处理,比如点击、刷新事件等 process_message 就会去处理这个事件,处理完了继续休息,retVal=0,程序就会一直执行,不会退出,这就是 RunLoop 作用。
RunLoop 的基本作用
1.保持程序的持续运行
2.处理 App 中的各种事件(比如触摸事件、定时器事件等)
3.节省了 CPU 资源,提高程序性能:该做事时做事,该休息时休息
...
获取 RunLoop 对象
iOS 中有 2 套 API 来访问和使用 RunLoop :
Foundation : NSRunLoop (OC 语言里面的)
Core Foundation : CFRunLoopRef (C 语言里面的)
NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象
NSRunLoop 是基于 CFRunLoopRef 的一层 OC 包装
CFRunLoopRef 是开源的.(CFRunLoopRef 参考链接)
其实我们很多都是由 OC 包装出来的,请看下面:
获取当前的 RunLoop
获取当前 RunLoop 和主线程 RunLoop
获取 RunLoop
这里注意 “地址不一样” 因为 NSRunLoop 是对 CFRunLoopDef 做了一层包装,你可以用 OC 的 NSLog("%@",[NSRunLoop MainRunLoop]) 获取对比一下,它的地址就是 C 语言获取的地址。主线程只有一个 RunLoop。
RunLoop 与线程
每条线程都有唯一的一个与之对应的 RunLoop 对象(一一对应)
RunLoop 保存在一个全局的 Dictionary 里,线程作为 key,RunLoop 作为 value
线程刚创建的时候并没有 RunLoop 对象,RunLoop 会在第一次获取它时创建
RunLoop 会在线程结束时销毁
主线程的 RunLoop 已经自动创建,子线程默认没有开启 RunLoop。
源码窥探看一下:CFRunLoopGetCurrent
由于源码不能像 objc 直接打开,我们把它拉到项目中查看。
从字典也能看出来是一对一的关系。而且确实是第一次获取的时候是空的,然后再去创建这个 RunLoop。
那我们就继续来了解 RunLoop 内部的数据结构,到底是怎么工作的。
RunLoop 相关的类
Core Foundation 中关于 RunLoop 的 5 个类
1.CFRunLoopRef
2.CFRunLoopModeRef
3.CFRunLoopSourceRef
4.CFRunLoopTimerRef
5.CFRunLoopObserverRef
再看下 CFRunLoopRef 的底层源码:
就是上面这个结构体,我们用到的可能就是红色这些.pthread 是线程,每个 runloop 都会保存这个东西。最后面那个 _modes,这个是个集合来着,CFMutableSetRef 我们能想到我们自己用的 set 也是一个集合来着,比如 NSMutableSet 也是一个集合,所以这个 _modes 里面是存着一堆的 mode。
这个 mode 就是 CFRunLoopModeRef 类型,所以里面存储一堆的 CFRunLoopModeRef 类型的 mode。
而 _currentMode 也是 CFRunLoopModeRef 这个类型,所以我们很容易得出一个结论:
一个 RunLoop 对象里面有一堆的 mode,也就是存在 _modes 里面,里面只有一个是 _currentMode。
我们再窥探一下源码,看下 mode 里面存储的是什么?
所以我们来个总结的图:
RunLoop 有很多种模式,对应的 _currentMode 只有一种。
CFRunLoopModeRef
1.CFRunLoopModeRef 它是代表 RunLoop 的运行模式;
2.一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source0/Source1/Timer/Observer;
3.RunLoop 启动时只能选择其中一个 Mode,作为 currentMode;
4.如果需要切换 Mode,只能退出当前 RunLoop,再重新选择一个 Mode 进入;
5.不同组的 Source0/Source1/Timer/Observer 能分割开来,互不影响;
6.如果 Mode 里面没有任何 Source0/Source1/Timer/Observer,RunLoop 会立马退出;
如果只能在一种模式下运行,对性能什么的都有很大好处,比如我在滑动模式下,不考虑不滑动的模式,所以就不会卡顿,顺畅很多。还有注意的就是,它切换 mode 是在循环里面切换的,所以不会导致程序退出。
常见的 mode 有 2 种,其他情况很少见,所以掌握这两个一般都是没问题了
1.KCFRunLoopDefaultMode (NSDefaultRunLoopMode):App 的默认 Mode,通常是主线程是在这个 Mode 下运行;
2.UITrackingRunLoopMode : 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响;
RunLoop 到底做哪些事?
RunLoop 在不停执行的时候到底具体做了哪些事?其实是 RunLoop 在不停循环的时候,就是处理每个 mode 下的 Source0、Source1、Timer、Observer 这里面的事件,那我们就来看看这里面具体对应的到底是什么事件。
Source0
触摸事件、performSelector:onThread:
比如我们的 touchbegin 这个我们看下下面的代码:
Source1
基于 Port 的线程间的通信,系统事件的捕捉。
(两个线程之间相互传递消息的处理,系统事件捕捉,其实也包括触摸事件,只是把事件捕捉到以后传递给 Source0)。
Timer
NSTimer 定时器,performSelector:withObject:afterDelay (这个方法的底层实现也就是 NSTimer 来实现的)。
Observers
用于监听 RunLoop 的状态,UI 的刷新 (BeforeWaiting),Autorelease pool(BeforeWaiting)。
(在 RunLoop 休眠之前都会去执行 UI 的刷新啊、Autorelease pool 的释放等)
以上这些东西,完全就是我们平时开发中经常写的代码,比如设置背景色,设置 frame 等等。
由于 RunLoop 知识点比较多,如果写太多不利于大家的阅读和消化,所以其他内容放在后面介绍!
转自:掘金 GDCoder
https://juejin.cn/post/6948676084879589389
全文完
以下文章您可能也会感兴趣:
我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。