RunLoop概述
macOS/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef
RunLoop指的是NSRunloop或者CFRunloopRef,CFRunloopRef是C语言的函数,而NSRunloop仅仅是CFRunloopRef的OC封装
源代码
RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件/消息,有事件/消息处理就保持运行,没有事件/消息处理的时候就会进入休眠状态,从而节省CPU资源,提高程序性能
RunLoop内部其实是一个_do while_循环,这也正是Runloop运行的本质。执行了这个函数以后就一直处于“等待-处理”的循环之中,直到循环结束。只是不同于我们自己写的循环它在休眠时几乎不会占用系统资源
- 没有事件/消息需要处理时,进入休眠避免资源占用
用户态 —> 内核态 - 有事件/消息需要处理时,立即被唤醒
内核态 —> 用户态
RunLoop运行逻辑,官方图
RunLoop结构
Runloop Mode
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
一个RunLoop包含了多个Mode,每个Mode又包含了若干个Source/Timer/Observer。每次调用 RunLoop的主函数时,只能指定其中一个Mode,这个Mode被称作CurrentMode。如果需要切换 Mode,只能退出Loop,再重新指定一个Mode进入。这样做主要是为了分隔开不同Mode中的Source/Timer/Observer,让其互不影响
- NSDefaultRunLoopMode
系统默认的Runloop Mode,在主线程运行。例如进入iOS程序默认不做任何操作就处于这种Mode中 - UITrackingRunLoopMode
滑动视图,主线程就切换Runloop到到UITrackingRunLoopMode,不再接受其他事件操作 - UIInitializationRunLoopMode
在刚启动App时第进入的第一个Mode,启动完成后就不再使用 - GSEventReceiveRunLoopMode
用于接受系统事件,属于内部的RunLoop模式 - NSRunLoopCommonModes
一种组合模式,包含了NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode
Source
Source事件分为Source0和Source1
- Source0 非系统事件,只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
- Source1 系统事件,包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。
Observe
Observe消息监听器
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
Observe状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将开始Timer处理
kCFRunLoopBeforeSources = (1UL << 2), // 即将开始Source处理
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //从休眠状态唤醒
kCFRunLoopExit = (1UL << 7), //退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Call out
事件/消息回调(无论是Observer的状态通知还是Timer、Source的处理)
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();\
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();\
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();\
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();\
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();\
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();\
RunLoop与线程
苹果只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent(), 这两个函数内部的实现逻辑大概是下面这样:
/// 全局的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());
}
从上面代码得知:
- runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务
- 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里
- runloop在第一次获取时被创建,在线程结束时被销毁。
RunLoop底层实现
RunLoop 实现是基于 mach port,RunLoop 的核心就是一个 mach_msg(),RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。
为了实现消息的发送和接收,mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图:
RunLoop应用
-
AutoreleasePool
-
事件响应
-
定时器
-
网络请求
-
Texture
……