1.什么是 RunLoop?


Run 表示运行,Loop 表示循环。结合在一起就是运行循环的意思。RunLoop 就是在程序运行过程中循环做一些事情.

2.RunLoop 的应用范畴有哪些?


定时器 (Timer)、PerformSelector

GCD Async Main Queue

事件响应、手势识别、界面刷新

网络请求

AutoreieasePool

上面这些底层都是 RunLoop 在支撑,说白了,如果没有 RunLoop 支撑,上面的这些都无法实现。

如果没有 RunLoop 会发生什么呢?像我们的命令行项目,创建出来默认就是没有 RunLoop,请看下图




IOS开发视频特效_ffmpeg


因为没有 RunLoop,程序执行到第 13 行的时候,就会自动退出.

而我们 iOS 项目的 main 函数里面都有 UIApplicationMain(argc, argv, nil, appDelegateClassName);这个代码,这里就是创建了一个主线程的 RunLoop,所以我们程序不会退出,一直在运行中。我们可以大致写一下 main 函数里面的伪代码如下:


IOS开发视频特效_ios_02


retVal 这个等于 0,当没有事件处理的时候,RunLoop 就会 sleep 就是类似睡觉,一旦有事件需要处理,比如点击、刷新事件等 process_message 就会去处理这个事件,处理完了继续休息,retVal=0,程序就会一直执行,不会退出,这就是 RunLoop 作用。

3.RunLoop 的基本作用


1.保持程序的持续运行

2.处理 App 中的各种事件(比如触摸事件、定时器事件等)

3.节省了 CPU 资源,提高程序性能:该做事时做事,该休息时休息

...

4.获取 RunLoop 对象


iOS 中有 2 套 API 来访问和使用 RunLoop :

Foundation : NSRunLoop (OC 语言里面的)

Core Foundation : CFRunLoopRef (C 语言里面的)

NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象

NSRunLoop 是基于 CFRunLoopRef 的一层 OC 包装

CFRunLoopRef 是开源的.(CFRunLoopRef 参考链接)

其实我们很多都是由 OC 包装出来的,请看下面:


IOS开发视频特效_Powered by 金山文档_03


获取当前的 RunLoop

获取当前 RunLoop 和主线程 RunLoop


IOS开发视频特效_音视频_04


获取 RunLoop

这里注意 “地址不一样” 因为 NSRunLoop 是对 CFRunLoopDef 做了一层包装,你可以用 OC 的 NSLog("%@",[NSRunLoop MainRunLoop]) 获取对比一下,它的地址就是 C 语言获取的地址。主线程只有一个 RunLoop。

5.RunLoop 与线程


每条线程都有唯一的一个与之对应的 RunLoop 对象(一一对应)

RunLoop 保存在一个全局的 Dictionary 里,线程作为 key,RunLoop 作为 value

线程刚创建的时候并没有 RunLoop 对象,RunLoop 会在第一次获取它时创建

RunLoop 会在线程结束时销毁

主线程的 RunLoop 已经自动创建,子线程默认没有开启 RunLoop。


IOS开发视频特效_Powered by 金山文档_05


源码窥探看一下:CFRunLoopGetCurrent

由于源码不能像 objc 直接打开,我们把它拉到项目中查看。


IOS开发视频特效_Powered by 金山文档_06


IOS开发视频特效_音视频_07


从字典也能看出来是一对一的关系。而且确实是第一次获取的时候是空的,然后再去创建这个 RunLoop。

那我们就继续来了解 RunLoop 内部的数据结构,到底是怎么工作的。

6.RunLoop 相关的类


Core Foundation 中关于 RunLoop 的 5 个类

1.CFRunLoopRef

2.CFRunLoopModeRef

3.CFRunLoopSourceRef

4.CFRunLoopTimerRef

5.CFRunLoopObserverRef

再看下 CFRunLoopRef 的底层源码:


IOS开发视频特效_ffmpeg_08


就是上面这个结构体,我们用到的可能就是红色这些.pthread 是线程,每个 runloop 都会保存这个东西。最后面那个 _modes,这个是个集合来着,CFMutableSetRef 我们能想到我们自己用的 set 也是一个集合来着,比如 NSMutableSet 也是一个集合,所以这个 _modes 里面是存着一堆的 mode。

这个 mode 就是 CFRunLoopModeRef 类型,所以里面存储一堆的 CFRunLoopModeRef 类型的 mode。

而 _currentMode 也是 CFRunLoopModeRef 这个类型,所以我们很容易得出一个结论:

一个 RunLoop 对象里面有一堆的 mode,也就是存在 _modes 里面,里面只有一个是 _currentMode。

我们再窥探一下源码,看下 mode 里面存储的是什么?


IOS开发视频特效_ffmpeg_09


所以我们来个总结的图:


IOS开发视频特效_ios_10


RunLoop 有很多种模式,对应的 _currentMode 只有一种。

7.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 影响;

8.RunLoop 到底做哪些事?


RunLoop 在不停执行的时候到底具体做了哪些事?其实是 RunLoop 在不停循环的时候,就是处理每个 mode 下的 Source0、Source1、Timer、Observer 这里面的事件,那我们就来看看这里面具体对应的到底是什么事件。

Source0

触摸事件、performSelector:onThread:

比如我们的 touchbegin 这个我们看下下面的代码:


IOS开发视频特效_IOS开发视频特效_11


Source1

基于 Port 的线程间的通信,系统事件的捕捉。

(两个线程之间相互传递消息的处理,系统事件捕捉,其实也包括触摸事件,只是把事件捕捉到以后传递给 Source0)。

Timer

NSTimer 定时器,performSelector:withObject:afterDelay (这个方法的底层实现也就是 NSTimer 来实现的)。

Observers

用于监听 RunLoop 的状态,UI 的刷新 (BeforeWaiting),Autorelease pool(BeforeWaiting)。

(在 RunLoop 休眠之前都会去执行 UI 的刷新啊、Autorelease pool 的释放等)

以上这些东西,完全就是我们平时开发中经常写的代码,比如设置背景色,设置 frame 等等。