RunLoop(基础部分)
在很多地方,都能看到这个概念,却不知道到底是个什么玩意,网上写的也很抽象,理解起来很吃力,官方文档官方文档也只是泛泛而谈。
那么,什么是RunLoop?
苹果在取名方面我觉得非常不错,runloop就像字面上的意思一样运行循环,俗称“兜圈圈”。也就是死循环,在这个循环中,会去处理一系列的事件。假如说这个循环停止了,那么程序就已经停止了。
![](https://img.haomeiwen.com/i2363289/4b06fd18b1d948e2.png)
在创建一个工程,都会有个main函数,在这个函数里就是去运行程序的时候创建了个主线程的RunLoop,也是保持程序持续运行的关键。
![](https://img.haomeiwen.com/i2363289/bfeb31227df80602.png)
如果改成return 0;那么程序将不会持续运行,刚运行就停止了。也不会去处理app的各种事件,这也是为什么要去使用RunLoop了,同样,在其他操作系统上也有这个概念。
![](https://img.haomeiwen.com/i2363289/1fe561bf6ef030ef.png)
请问这个打印会正常打印出来么?可以先思考下为什么。
答案是不会打印的,因为在上一句,就成死循环,也就是弄了个runloop,一直卡在上一句代码,也就是保持了程序的持续运行,如果说能打印,那么return也能返回值,那么程序就没法持续保持运行,一旦返回值那么意味着结束。
RunLoop有什么用呢?
1.保持程序的持续运行
2.处理App的各种事件(比如触摸事件、定时器事件、Selector事件)
3.节省CPU资源,提高程序性能。(该做事的时候做事,该休息的时候休息)
RunLoop对象
在iOS中有2套API去访问和使用RunLoop
Foundation框架
NSRunLoop
Core Foundation框架
CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层OC封装,为此要理解RunLoop内部结构,要多研究CFRunLoopRef
NSRrunLoop缺点:效率低(因为是上层封装),封装的不算好,可提供的方法很少。
CFRunLoopRef是开源的,开源地址CFRunLoopRef开源地址
RunLoop和线程
1.每条线程都有唯一的一个与之对应的RunLoop对象
2.主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建(默认关闭的)
3.RunLoop在第一次获取时创建,在线程结束时销毁。
如何获取RunLoop对象
Foundation框架
[NSRunLoop currentRunLoop];//获取当前线程的RunLoop对象
[NSRunLoop mainRunLoop];//获取主线程的RunLoop对象
Core Foundation
CFRunLoopGetCurrent();//获取当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象
RunLoop相关类
![](https://img.haomeiwen.com/i2363289/f9016e13892b4cde.png)
Core Foundation中关于RunLoop有5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
RunLoop中有多个Mode,可以在多个Mode中来回切换,Mode是RunLoop重要的组成部分,没有Mode,RunLoop就跑不起来.
在一个Mode里,不是必须Source,Timer,Observer三个必须有值,有一个有值,RunLoop也是可以跑起来的。
CFRunLoopModeRef
1.CFRunLoopModeRef代表RunLoop的运行模式
2.一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
3.每次RunLoop启动时,只能指定其中一个Mode,这个Mode又叫做CurrentMode
4.如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入Loop。这样做主要是为了分隔不同组的Source/Timer/Observer,让其互不影响。
系统默认注册了5个Mode:
kCFRunLoopDefaultMode:app默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于Scrollview追踪触摸滑动,保证界面滑动不受其他Mode影响
UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode:接受系统时间的内部Mode,通常用不到
kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode,即可能kCFRunLoopDefaultMode,也可能是UITrackingRunLoopMode
例子:
![](https://img.haomeiwen.com/i2363289/238f1c83fdacdb2b.gif)
当不去滑动TextView时,当前RunLoopMode是NSDefaultMode,所以定时器会正常运作;当去滑动TextView时,当前RunLoopMode切换到UITrackingRunLoopMode,定时器又要在NSDefaultMode模式下才能运行,所以定时器失效;当不再滑动,又切换到NSDefaultMode,定时器又可以正常运作。
我们知道了是因为RunLoop模式导致了,所以,将定时器在的模式更改就行了。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef
Mode的一个组成部分,CFRunLoopSourceRef被称为事件源(输入源)。RunLoopSource包括2种source:分别为Source0和Source1。
Source0:非基于port的
只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用。(通俗点说:1.自定义的事件,自己写的事件;2.PerformSelecter事件)
Source1:基于port的
包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程(简单地说:系统做的事件)
(补充:port就是端口内核的意思,也可以理解为cpu)
![](https://img.haomeiwen.com/i2363289/a4d49d003e0af604.png)
上图是我在storyboard拖的button事件方法,通过设置断点,可以看到线程栈的情况,很清楚的看到Source0,也可以说明,事件源是由自定义写的。
CFRunLoopTimerRef
基于时间的触发器(说白了,基本就是NSTimer)
CFRunLoopObserverRef
CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变
可以监听的时间点有下面几个
/*Run Loop Observer Activities*/
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity){
kCFRunLoopEntry = (1UL << 0), //即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 8), //刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU //监听RunLoop所有情况
}
如何去创建一个ObserverRef
![](https://img.haomeiwen.com/i2363289/4410c59a969f0655.png)
列1:我们来看看应用从启动到界面显示,runloop状态是如何变化的。
![](https://img.haomeiwen.com/i2363289/81f9948fd009b5bd.png)
可以看到,从最开始的1(即将进入RunLoop)到最后的32(即将进入休眠)一个过程。
列1:我们来监听下一个按钮点击后,RunLoop状态的变化
![](https://img.haomeiwen.com/i2363289/89c890dbcf57aff6.png)
可以看到,从最开始的64(刚从休眠唤醒)到最后的32(即将进入休眠)。
为此,我们可以利用runloop的状态改变来处理一些不好处理的事件,具体需要具体情况而定,这样runloop就可以很好的结合起来使用了。
知道了RunLoop逻辑处理和RunLoop结构后,我们来看看官方提供的RunLoop处理逻辑图就很好理解了。
![](https://img.haomeiwen.com/i2363289/a19940e2a9dd1df1.png)
左图的就是RunLoop,Runloop是跟线程一一绑定的,所以是个Thread。然后Runloop一直跑圈,在跑圈的过程中,右图会有Sources传入进行事件操作,如Timer,基于Port事件,自定义事件,performSelector事件。
RunLoop处理逻辑过程
![](https://img.haomeiwen.com/i2363289/02a0675d4e412ead.png)
总结:
1.主线程的RunLoop默认开启
2.子线程的RunLoop默认关闭,需要手动开启
3.RunLoop开启的时候要包含Mode,必须要某个确定的模式下运行,同时在一个模式下运行
4.observer可以监听RunLoop所有状态事件,可以做到一些别人做不到的事。