一、Runloop基础概念
程序一般来说,运行完就会退出。所以iOS程序需要一个机制保证程序不退出保持运行状态,并可以随时响应用户触摸点击等操作。这就是Runloop。
Runloop的基本作用就是:
1.保持程序持续运行;在iOS程序中,程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,RunLoop保证主线程了不会被销毁,也就保证了程序的持续运行
2.响应App中的各种事件(比如:触摸事件,定时器事件等)
3.没有事件需要处理时进入休眠,节省CPU资源。
先看一下苹果官方的图片来简单了解一下RunLoop内部运行原理
二、Runloop与线程的关系
苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。这两个函数的实现逻辑大概是下面这样:
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (!__CFRunLoops) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
}
// 直接从一个全局的Dic根据线程获取Runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果线程中还没有runloop,会创建一个runloop,并与之关联。
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
return _CFRunLoopGet0(pthread_self());
}
CFRunLoopRef CFRunLoopGetMain(void) {
return _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
}
从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。
线程刚创建时并没有 RunLoop,RunLoop 的创建是发生在第一次获取时(getCurrent)。
三、Runloop的底层数据结构
Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
它们之间的关系如下:
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。
每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。
如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
- Source0:非基于Port的;用于用户主动触发的事件(点击button、点击屏幕、PerformSelector)
- Source1:基于Port的;通过内核和其他线程相互发送消息(与内核相关)
CFRunLoopTimerRef 是基于时间的触发器,当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
CFRunLoopObserverRef是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
四、Runloop运行逻辑
// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
...
// 通知observers:进入loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知observers:退出loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
...
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
...
int32_t retVal = 0;
do {
// 通知Observers:即将处理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers:即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理Source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 又处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// 如果有source1,跳转到handle_msg
goto handle_msg;
}
// 通知observers:即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 等待别的消息来唤醒当前线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 通知Observers:结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 收到消息,处理消息。
handle_msg:
__CFRunLoopSetIgnoreWakeUps(rl);
if (被Timer唤醒) {
// 处理Timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()
}
else if (被GCD唤醒) {
// 处理GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被Source1唤醒
// 处理Source1
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 设置返回值
...
} while (0 == retVal);
...
return retVal;
}
其实RunLoop 就是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。
Runloop在没有事件时,通过切换到内核态来休眠,节省CPU资源。
网友评论