概述
RunLoop顾名思义就是运行循环,来保证程序一直处于程序运行状态。
在iOS中,RunLoop有很多应用,比如:
- 定时器(Timer)、PerformSelector
- GCD、Async Main Quene
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
这些技术底层都会用到RunLoop。
在iOS项目中main
函数中都会这样写道:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
UIApplicationMain()
函数中内部就会为主线程创建一个RunLoop。对于iOS程序,RunLoop也是非常重要的,比如:
- 保持程序的持续运行
- 处理App中各种事件(比如触摸事件、定时器事件)
- 节省CPU资源,提高程序性能:该处理事件时唤醒,不处理时wait。
RunLoop
iOS中有2套API可以访问和使用RunLoop
- Foundation: NSRunLoop
- Core Foundation: CFRunLoopRef
NSRunLoop
是基于CFRunLoopRef
的一层OC包装
//获取当前线程的RunLoop对象
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
//获取主线程的RunLoop对象
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
//获取当前线程的RunLoop对象
CFRunLoopGetCurrent();
//获取主线程的RunLoop对象
CFRunLoopGetMain();
RunLoop与线程
RunLoop和线程有这密切的关系
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key, RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop。
对于上述的这些关系,我们可以在开源的Core Foundation中窥见一二:
// CFRunLoop.c
// 代码已经简化
CFRunLoopRef CFRunLoopGetCurrent(void) {
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 在全局RunLoop字典 __CFRunLoops 中,以线程作为key,获取RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 如果loop为空
if (!loop) {
//则新创建loop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
//以线程作为key,存入全局RunLoop字典中
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
CFRelease(newLoop);
}
}
RunLoop相关的类
Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
其中RunLoop
的底层结构为:
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread; //与该Runloop对应的线程
CFMutableSetRef _commonModes; //被标记为commonModes的Mode
CFMutableSetRef _commonModeItems; //被添加到commonModes的事件源
CFRunLoopModeRef _currentMode; //表示该RunLoop中正在运行的Modes
CFMutableSetRef _modes; //CFRunLoopModeRef类型,表示该Runloop中包含的Modes
}
CFRunLoopModeRef
CFRunLoopModeRef
的底层结构为:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _source0;
CFMutableSetRef _source1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
}
- CFRunLoopModeRef代表RunLoop的运行模式
- 一个RunLoop包含若干个Mode,每个Mode有包含若干个Source0/Source1/Timer/Observer
- RunLoop启动时只能选择其中一个Mode,作为currentMode
- 如果需要切换Mode,只能退出当Loop
- 不同Mode的Source0/Source1/Timer/Observer能分隔开,互不影响
- 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的2种Mode
系统默认提供的Run Loop Modes有:
kCFRunLoopDefaultMode(NSDefaultRunLoopMode)
UITrackingRunLoopMode
需要切换到对应的Mode时只需要传入对应的名称即可。前者是系统默认的Runloop Mode,例如进入iOS程序默认不做任何操作就处于这种Mode中,此时滑动UIScrollView,主线程就切换Runloop到到UITrackingRunLoopMode,不再接受其他事件操作(除非你将其他Source/Timer设置到UITrackingRunLoopMode下)。
但是对于开发者而言经常用到的Mode还有一个kCFRunLoopCommonModes(NSRunLoopCommonModes),其实这个并不是某种具体的Mode,而是一种模式组合,在iOS系统中默认包含了NSDefaultRunLoopMode和UITrackingRunLoopMode
注意:并不是说Runloop会运行在kCFRunLoopCommonModes这种模式下,而是相当于分别注册了NSDefaultRunLoopMode和UITrackingRunLoopMode。当然你也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到kCFRunLoopCommonModes组合。
注意:我们常常还会碰到一些系统框架自定义Mode,例如Foundation中NSConnectionReplyMode。还有一些系统私有Mode,例如:GSEventReceiveRunLoopMode接受系统事件,UIInitializationRunLoopMode App启动过程中初始化Mode。
Source0/1 & Timers & Observer
- Source0
- 触摸事件的处理
performSelector:onThread:
- Source1
- 基于Port的线程间通信
- 系统事件捕捉,比如点击事件,捕捉后封装成Source0
- Timers
- NSTimer
-
performSelector:withObject:aferDelay
- 内部是用定时器实现
- Observer
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting,睡眠之前)
- Autorelease pool(BeforeWaiting,睡眠之前)
CFRunLoopObserverRef
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; /* 监听的RunLoop */
CFIndex _rlCount;
CFOptionFlags _activities; /* 监听的RunLoop状态 */
CFIndex _order; /* 创建时传入的排序 */
CFRunLoopObserverCallBack _callout; /* 回调 */
CFRunLoopObserverContext _context; /* 回调参数 */
};
CFRunLoopObserverRef可以用来监听RunLoop的状态,RunLoop的状态分为以下几种:
创建自定义Observer
创建自定义Observer有两种方式
-
第一种:使用回调函数创建
/// 1. 创建observer // 第一个参数:分配存储空间,使用默认的即可:kCFAllocatorDefault // 第二个参数:要监听的状态(kCFRunLoopAllActivities)所有的状态 // 第三个参数:是否持续监听 // 第四个参数:优先级,填0即可 // 第五个参数:回调,CFRunLoopObserverCallBack类型, // typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info); // 第六个参数:context,回调传参,可以NULL CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observerCallBack, NULL); /// 2.将observer添加到RunLoop中, // kCFRunLoopCommonModes默认包括kCFRunLoopDeaultMode、UITrackingRunLoopMode CFRunLoopAddObserver(CFRunLoopGetMain(), observerRef, kCFRunLoopCommonModes); /// 3. 释放observer CFRelease(observerRef); /// 回调函数 /// @param observer observer /// @param activity Loop activity /// @param info 创建observer时,传入的context void observerCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { switch (activity) { case kCFRunLoopEntry: NSLog(@"kCFRunLoopEntry,即将进入Loop"); break; case kCFRunLoopBeforeTimers: NSLog(@"kCFRunLoopBeforeTimers,即将处理Timer"); break; case kCFRunLoopBeforeSources: NSLog(@"kCFRunLoopBeforeSources,即将处理Source"); break; case kCFRunLoopBeforeWaiting: NSLog(@"kCFRunLoopBeforeWaiting,即将进入Loop"); break; case kCFRunLoopAfterWaiting: NSLog(@"kCFRunLoopAfterWaiting,刚从睡眠中唤醒"); break; case kCFRunLoopExit: NSLog(@"kCFRunLoopExit,即将退出Loop"); break; default: break; } }
-
第二中:使用Block回调创建
/// 1. 创建observer // 第一个参数:分配存储空间,使用默认的即可:kCFAllocatorDefault // 第二个参数:要监听的状态(kCFRunLoopAllActivities)所有的状态 // 第三个参数:是否持续监听 // 第四个参数:优先级,填0即可 // 第五个参数:block回调 CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"kCFRunLoopEntry,即将进入Loop"); break; case kCFRunLoopBeforeTimers: NSLog(@"kCFRunLoopBeforeTimers,即将处理Timer"); break; case kCFRunLoopBeforeSources: NSLog(@"kCFRunLoopBeforeSources,即将处理Source"); break; case kCFRunLoopBeforeWaiting: NSLog(@"kCFRunLoopBeforeWaiting,即将进入Loop"); break; case kCFRunLoopAfterWaiting: NSLog(@"kCFRunLoopAfterWaiting,刚从睡眠中唤醒"); break; case kCFRunLoopExit: NSLog(@"kCFRunLoopExit,即将退出Loop"); break; default: break; } }); /// 2.将observer添加到RunLoop中, // kCFRunLoopCommonModes默认包括kCFRunLoopDeaultMode、UITrackingRunLoopMode CFRunLoopAddObserver(CFRunLoopGetMain(), observerRef, kCFRunLoopCommonModes); /// 3. 释放observer CFRelease(observerRef);
RunLoop的运行逻辑
源码分析
我们可以在ViewController
中viewDidLoad
打一个断点,利用lldb指令:bt查看程序调用栈
可以看出,进入RunLoop逻辑的是CFRunLoopRunSpecific
函数。下面是伪代码开始分析:
// CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
//通过Mode名称 ,获取RunLoop当前的Mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//通知observer: 进入Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知observer: 退出Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
__CFRunLoopRun():
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 通知observer: 即将处理Timer
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知observer: 即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 通知observer: 即将处理block
__CFRunLoopDoBlocks(rl, rlm);
// 通知observer: 处理Source0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
// 通知observer: 即将处理block
__CFRunLoopDoBlocks(rl, rlm);
}
// 判断有无Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//如果有就跳转到 handle_msg
goto handle_msg;
}
// 通知observer: 即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
//等待别的消息来唤醒当前线程,线程阻塞在这里
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 如果被唤醒,继续执行
__CFRunLoopUnsetSleeping(rl);
// 通知observer: 结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
if (/* 被timer唤醒 */) {
//处理timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()
} else if (/* 被timer唤醒 */) {
// 处理GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { /* 被Source1唤醒 */
// 处理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 设置返回值
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
RunLoop 休眠
其实对于Event Loop而言RunLoop最核心的事情就是保证线程在没有消息时休眠以避免占用系统资源,有消息时能够及时唤醒。RunLoop的这个机制完全依靠系统内核来完成,具体来说是苹果操作系统核心组件Darwin中的Mach来完成的(Darwin是开源的)。
Mach是Darwin的核心,可以说是内核的核心,提供了进程间通信(IPC)、处理器调度等基础服务。在Mach中,进程、线程间的通信是以消息的方式来完成的,消息在两个Port之间进行传递(这也正是Source1之所以称之为Port-based Source的原因,因为它就是依靠系统发送消息到指定的Port来触发的)。消息的发送和接收使用<mach/message.h>中的mach_msg()函数(事实上苹果提供的Mach API很少,并不鼓励我们直接调用这些API)。
/*
* Routine: mach_msg
* Purpose:
* Send and/or receive a message. If the message operation
* is interrupted, and the user did not request an indication
* of that fact, then restart the appropriate parts of the
* operation silently (trap version does not restart).
*/
__WATCHOS_PROHIBITED __TVOS_PROHIBITED
extern mach_msg_return_t mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);
而mach_msg()的本质是一个调用mach_msg_trap(),这相当于一个系统调用,会触发内核状态切换。当程序静止时,RunLoop停留在
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy)
而这个函数内部就是调用了mach_msg()让程序处于休眠状态。