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 是 CFRunLoopRefstatic 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);复制代码