RunLoop是一个对象,用于处理程序运行中出现的各种事件(触摸事件、定时器、Selector),从而保证程序的持续运行;特点是没有事件处理的时候,会让线程进入休眠模式,从而节约CPU资源,提好程序性能。
// defaultMode启动
void CFRunLoopRun(void) {
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
从上面代码中可以看出runloop本质上就是do...while的循环,通过阀值kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result控制代码的运行。
1.线程与runloop的关系
- 主线程默认开启线程RunLoop,获取方法
CFRunLoopGetMain(); - 苹果不允许直接创建线程RunLoop,在子线程中可以使用
CFRunLoopGetCurrent获取子线程的RunLoop对象; - 线程与RunLoop关系是一一对应的,其关系保存在一个全局的字典中,子线程的RunLoop的创建是第一次获取时,销毁是在线程结束时。
CFRunLoopGetCurrent 调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。
主线程CFRunLoopRef相关源码:
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程 根据传入的主线程创建主线程对应的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
子线程CFRunLoopRef相关源码:
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
// 加锁
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
2. CFRunLoopMode类型
- kCFRunLoopDefaultMode:
默认mode, 主线程一般在这个mode下运行 - UITrackingRunLoopMode:
scrollView追踪触摸滑动,保证界面滑动中不受其他mode影响 - UIInitializationRunLoopMode:
App启动时进入的第一个mode,后面用不到 - GSEventReceiveRunLoopMode:
系统内部时间mode - kCFRunLoopCommonModes:
占位mode,标记kCFRunLoopDefaultMode和UITrackingRunLoopMode,不是真正的mode
3. CFRunLoopMode和CFRunLoop关系
CFRunLoopMode源码:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
CFRunLoop源码:
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
CFRunLoopSourceRef
- 是事件产生的地方。Source有两个版本:Source0 和 Source1。
- Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
- Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程.
CFRunLoopTimerRef
基于时间触发器,可以和NSTimer混用。包含一个时间长度和一个函数指针(用于回调)。将其加入RunLoop时,RunLoop会注册这个时间点。时间点到时,RunLoop会被唤醒执行回调函数。
CFRunLoopObserverRef
观察者(观察RunLoop的状态),每个Observer都包含一个函数指针(用于回调),当RunLoop的状态发生改变时,观察者可以通过回调接收这个变化。
观察的时间点:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
流程如图:
一个RunLoop包含若干mode,每个mode又包含observer\timer\source,调用RunLoop的主函数是,只能指定一种mode即CurrentMode。如果切换mode,只能先退出Loop,重新指定一个mode,目的是分隔不同组的observer\timer\source,让其不互相影响。
Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
CFRunLoop对外暴露的管理 Mode 接口:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
Mode 暴露的管理 mode item 的接口:
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);