一、什么是RunLoop
iOS的RunLoop简单来说就是一个运行循环,在需要的时候运行,在不需要的时候进行休眠,节省CPU资源。
下面是苹果官方文档的一张解释RunLoop的图示,大概展示了RunLoop的工作原理。
从上面的图可以看出RunLoop的大概工作原理,当接收到Input sources 或者 Timer sources时就会交给对应的处理方去处理。当没有事件消息传入的时候,RunLoop就进入休眠状态。runloop处于监视与被监视的状态中,要处理很多复杂的事件,下面会详细介绍runloop的组成。
input source 和 timer source
input source 和 timer source 都是runloop的事件源,其中input source又可以分为三类:
- Port-Based Source:系统底层的的Port事件(在应用层基本不会碰到);
- Custom Input Source:用户手动创建的Source;
- Cocoa Perform Selector Sources,Cocoa提供的 performSelector 系列方法,也是一种事件源
Timer Source 也就是定时器事件。
RunLoop Observer
runloop observer是runloop本身的监视器,它可以监视runloop自身的状态。runloop observer 可以监视下面的runloop事件
- The entrance to the run loop. (进入循环)
- When the run loop is about to process a timer.(循环将要处理 timer 事件源)
- When the run loop is about to process an input source.(循环将要处理input source事件源)
- When the run loop is about to go to sleep.(循环将要进入休眠)
- When th run loop has woken up,but before it has processed th event that woke it up.(循环已经被唤醒,但是还没有处理唤醒该循环的事件)
- The exit from th run loop.(退出循环)
Runloop Mode
先上一个图
Runloop Mode 实际上是Source,Timer 和Observer的组合。不同的Mode讲不同组的Source, Timer,Observer打包与其他Mode的内部成员隔绝开来。这样Runloop在某个时刻就只能跑在一个Mode下,专心处理该Mode下的Source,Timer和Observer。
苹果官方文档中提到的Mode有5种,分别是:
- NSDefaultRunLoopMode
- NSConnectionRepLyMode
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode
- NSRunLoopCommonModes
iOS 中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。 NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。
当然,RunLoop Mode不止上面提到的5种,我们来看下系统开启后,都默认注册了哪些Mode。
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
二、RunLoop和线程有什么关系
runloop的作用就是管理线程,当线程的runloop开启后,县城就会在执行完任务后进入到休眠状态,随时等待新的事件源进入,而不是退出。
runloop和线程一一对应,每个线程只有一个runloop。线程在创建的时候并没有创建runloop,需要手动创建(主线程除外)。iOS中没有创建runloop的方法,是通过获取时懒加载实现的。只能在线程内部获取改线程的runloop(主线程除外)。
三、iOS中提供的RunLoop对象及其访问方法
RunLoop对象
iOS中提供了两个RunLoop对象
- CFRunLoopRef : CorFoundation框架内,提供纯C函数的API,所有这些API都是线程安全的。
- NSRunLoop : 基于CFRunLoopRef的封装,提供了面向对象的API,但是这些API不是线程安全的。
获取RunLoop对象。
苹果不允许直接创建runloop,只提供了获取Runloop的函数:
Core Foundation
CFRunLoopGetCurrent() //获取当前线程的RunLoop对象 CFRunLoopGetMain() //获取主线程的RunLoop对象
Foundation
[NSRunLoop currentRunLoop] //获取当前线程的RunLoop对象 [NSRunLoop MainRunLoop] //获取主线程的RunLoop对象
RunLoop相关的类
Core Foundation中关于RunLoop的5个类
CFRunLoopRef
- 用来获取当前的RunLoop和主RunLoop
CFRunLoopModeRef
- RunLoop的运行模式,一个RunLoop包含若干个Mode;
- 每个Mode包含若干个 Source/Timer/Observer,每次RunLoop启动时,只能制定一个Mode运行,这个Mode被称之为CurrentMode;
- 如果需要切换Mode,只能退出RunLoop,重新制定一个Mode进入;
- 这样可以分隔开不同组的Source/Timer/Observer互不影响。
- CFRunLoopSourceRef
- CFRunLoopSourceRef 是事件源
- 按照官方文档分类:
- Port-Based Sources(基于端口,跟其他线程交互,通过内核发布的消息);
- Custom Perform Selector Sources(自定义);
- Cocoa Perform Selector Sources(PerformSelector方法)
- 按照函数调用栈的分类
- Source0:非基于Port
- Source1:基于Port
- CFRunLoopTimerRef
- CFRunLoopTimerRef是基于时间的触发器
- 基本上说的就是NSTimer(CADisplayLink也是加到RunLoop),它受RunLoop Mode的影响。
- CFRunLoopObserveRef
- CFRunLoopObserverRef是观察者类,负责监听RunLoop的状态改变;
- 可以监视上面 RunLoop Observer中提到的几个状态变化。
四、Runloop的实际应用
在线程中使用timer
这应该是最常见的runloop的应用。一个NSTimer一次只能加入到一个runloop中。我们日常使用的时候,通常是加入到当前的runloop的default mode中,而scrollView在用户滑动的时候,主线程的runloop就会跳转到UITrackingRunLoopMode。而这个时候 timer就不会运行。针对这种情况有两种解决方案:
- 设置Runloop Mode,例如NSTimer,我们指定它运行在NSRunLoopCommonModes,这是一个Mode的集合。注册到这个Mode下后,无论当前的runloop运行哪个Mode,事件都能得到执行。
- 另一种解决timer的办法就是,我们在另一个线程执行和处理timer事件,然后在主线程更新UI。
将有频繁操作的回调指定到固定的线程中执行。这样可以减少线程创建和销毁的开销。