文章目录
RunLoop是什么
如果没有RunLoop,那么该线程在执行完自己的任务时就会退出;而我们在使用APP时,通常不会只执行完一个任务就需要其退出,那么我们就需要一种机制,在我们没有任务时依然可以保证线程不退出,随时可以处理事件,这就需要用到RunLoop了。
RunLoop,顾名思义:运行循环,在其内部就是一个大的do-while循环,在这个循环里执行了各种操作,保证线程不退出,程序持续运行。其存在的目的就是:在保证线程不退出的前提下,当线程有任务时执行任务,在线程没有任务的时候就让其睡眠,节省CPU资源,提高程序性能。
NSRunLoop和CFRunLoop
苹果官方源码
我们平时所说的RunLoop有两种,一种是NSRunLoop,一种是CFRunLoop
将下列代码写在主线程中,即主RunLoop和当前的RunLoop是同一个
NSLog(@"%p %p", [NSRunLoop mainRunLoop], [NSRunLoop currentRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());
打印结果显示NSRunLoop和CFRunLoop地址不一样,再打印NSRunLoop的信息看看
NSLog(@"%@", [NSRunLoop mainRunLoop]);
NSLog(@"%p %p", CFRunLoopGetMain(), CFRunLoopGetCurrent());
根据箭头所指可以得知,NSRunLoop只是比CFRunLoop多了一层简单的OC封装,底层还是CFRunLoop,CFRunLoop本质是一个结构体,而NSRunLoop是一个NSObject对象。且NSRunLoop存在于Foundation框架中,CFRunLoop是存在于CoreFoundation框架中的。NSRunLoop不是线程安全的,CFRunLoop时候线程安全的。那我们现在看看CFRunLoop是如何实现的
RunLoop内部结构
在分析源码之前,我们先大致了解一下RunLoop的内部结构。CoreFoundation里面关于RunLoop有五个类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
他们之间的关系为:(图来源于深入理解RunLoop)
即一个RunLoop里有许多个Mode,一个Mode里有一个Source的set,一个Observer的array和一个Timer的array。这里要注意,虽然一个RunLoop里存在许多Mode,但是每次只能使用一个Mode,需要更换时就退出当前Mode,换下一个Mode
RunLoop基本结构体源码
__CFRunLoop结构体
我们先看看__CFRunLoop结构体是怎么组成的
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */ // 访问mode集合时用到的锁
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp // 手动唤醒runloop的端口。初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop 初始化runloop时设置,仅用于CFRunLoopWakeUp,CFRunLoopWakeUp函数会向_wakeUpPort发送一条消息
pthread_t _pthread; // 与该RunLoop关联的线程
uint32_t _winthread;
CFMutableSetRef _commonModes; // 存储的是字符串,记录所有标记为common的mode
CFMutableSetRef _commonModeItems; // 存储所有commonMode的item(source、timer、observer)
CFRunLoopModeRef _currentMode; // 当前运行的mode
CFMutableSetRef _modes; // 该RunLoop中所有的mode
struct _block_item *_blocks_head; // 链表头指针,该链表保存了所有需要被runloop执行的block。外部通过调用CFRunLoopPerformBlock函数来向链表中添加一个block节点。runloop会在CFRunLoopDoBlock时遍历该链表,逐一执行block
struct _block_item *_blocks_tail; // 链表尾指针,之所以有尾指针,是为了降低增加block时的时间复杂度
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
从__CFRunLoop结构体可以得知:
- RunLoop和线程是一一对应的,一个RunLoop包含一个线程,当该线程结束时RunLoop自动销毁
- 一个RunLoop只有一个_currentMode,即当前运行的mode
- _commonModes 表示NSRunLoopCommonModes这个模式下保存的Mode,我们也可以将自定义的Mode添加到这个set里面
- _commonModeItem 表示添加到NSRunLoopCommonModes里面的Source/Timer/Observer等
- _commonModes里面保存的是被标记为common的mode。这种标记为common的mode有种特性,那就是当 RunLoop 的内容发生变化时,RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。可以这样理解,RunLoop中的_commonModeItems由被标记为common的mode下的各个item(source、observe、timer)组成
__CFRunLoopMode结构体
上文谈到了许多次mode,现在看看mode是怎么组成的:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // mode名称
Boolean _stopped; // mode是否被终止
char _padding[3];
// 几种事件,下面这四个字段,在苹果官方文档里面称为Item
// RunLoop中有个commomitems字段,里面就是保存的下面这些内容
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
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 */
};
需要注意:
- 一个CFRunLoopMode对象中有一个name,许多source0,许多source1,许多Timer,许多Observer和许多port
- 我们在下文会提到的kCFRunLoopDefaultMode就是mode的name,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef
- 对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除
- source、timer、observer可以在多个mode中注册,但是只有RunLoop当前的currentMode下的source、timer、observer才可以运行
RunLoop中的mode主要分为以下几种:
- KCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动的时候不受其他Mode影响
- KCFRunLoopCommonMode:这是一个占位用的Mode,作为标记KCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode(也可以理解为是上面两种mode的结合)
- UIInitializationRunLoopMode:在刚启动App的时候进入的第一个Mode,启动完成后不再使用
- CSEventReceiveRunLoopMode:接受系统事件内部Mode,通常用不到
综合以上,我们可以得知,RunLoop里只保存了许多mode,而mode里保存的才是实际执行的任务
接下来我们剖析mode里包含的三种item
__CFRunLoopSource结构体
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
// version0、version1 是根据对不同事件的处理区分出来的source0、source1
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
可以看出,Source根据要处理的事件不同分为两个版本,version0和version1,我们分别探究两个的结构体:
CFRunLoopSourceContext
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode); //当source加入到mode触发的回调
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode); //当source从RunLoop中移除时触发的回调
void (*perform)(void *info); //当source事件被触发时的回调,使用CFRunLoopSourceSignal方式触发
} CFRunLoopSourceContext;
即:
source0 只包含了回调(函数指针),source0是需要手动触发的Source,它并不能主动触发事件,必须要先把它标记为signal状态。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,也就是通过uint32_t _bits来实现的,只有_bits标记Signaled状态才会被处理。然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件
CFRunLoopSourceContext1
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info); //当source被添加到mode中的时候,从这个函数中获得具体mach_port_t
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
source1除了多个了getPort。其余的字段含义和source0相同。作用就是当source被添加到mode中的时候,从这个函数中获得具体mach_port_t。这种 Source 能主动唤醒 RunLoop 的线程。简单来说就是更加偏向于底层
__CFRunLoopTimer结构体
__CFRunLoopTimer主要处理RunLoop的相关Timer事件,定时器,定时执行一个任务,也是在RunLoop中处理的
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits; //标记fire状态
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //添加该timer的RunLoop
CFMutableSetRef _rlModes; //存放所有包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; //理想时间间隔 /* immutable */
CFTimeInterval _tolerance; //时间偏差 /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; //设置回调函数,回调指针 /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
Timer包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。如果当前时间点正在执行别的任务,会直接越过Timer的任务。类似于在18:00和18:10都有公交车,你玩手机错过了18:00的车就直接等18:10的车
注意: NSTimer在添加到runloop中的时候要用NSRunLoopCommonModes作为参数。
理由:并且NSTimer创建好之后默认是加入名为kCFRunLoopDefaultMode的model中,所以只有应用程序在kCFRunLoopDefaultMode下,Timer才会工作。如果这个时候滑动了屏幕,那么应用的mode就从名字为kCFRunLoopDefaultMode的mode中退出,进入到名称为UITrackingRunLoopMode的model中。因为NSTimer没有添加到名字为UITrackingRunLoopMode的item中,所以只有等到不再滑动,即回到kCFRunLoopDefaultMode的时候才再次开始工作。通过NSRunLoopCommonModes标记之后,Runloop会把NSTimer加入到Runloop中的commonModeItems中。上面讲过RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Model里。
__CFRunLoopObserver结构体
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //添加该Observer的RunLoop
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; //设置回调函数,回调指针 /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
CFRunLoopObserver是观察者,可以观察RunLoop的各种状态,每个 Observer 都包含了一个回调(也就是上面的CFRunLoopObserverCallBack函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。RunLoop在运行过程中有以下几个状态:
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
};
RunLoop获取源码
上文中提到过CFRunLoopGetMain()和CFRunLoopGetCurrent()都可以获取RunLoop,前者用于获取主线程RunLoop,后者用于获取当前线程。我们查看一下两者的源码
CFRunLoopGetMain()和CFRunLoopGetCurrent()源码
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
可以看出,两个都是通过函数_CFRunLoopGet0()来获取RunLoop的,那么我们看看_CFRunLoopGet0()的源码
_CFRunLoopGet0()源码
// 声明一个全局的可变字典 __CFRunLoops用于存储每一条线程和对应的runloop
// __CFRunLoops的键key存储线程pthread_t,值存储runloop
static CFMutableDictionaryRef __CFRunLoops = NULL;
// loopsLock用于访问 __CFRunLoops时加锁,锁的目的是:考虑线程安全问题,防止多条线程同时访问 __CFRunLoop对应的内存空间
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 判断t所指向的线程是否为空,如果为空,就获取当前主线程进行赋值
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
// 处理线程安全,加锁
__CFLock(&loopsLock);
// 判断全局的可变字典 __CFRunLoops是否为空
// 如果为空
if (!__CFRunLoops) {
// 解锁
__CFUnlock(&loopsLock);
// 定义一个局部可变字典dict
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建一个与主线程对应的RunLoop对象mainLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 把主线程和主线程对应的mainLoop存储于字典dict
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 把局部创建的字典dict赋值给全局字典__RunLoops
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
// 赋值成功后,release局部变量
CFRelease(dict);
}
// 存储mainLoop到字典以后,撤销一次引用,即release一次mainLoop,因为mainLoop存储到字典后会自动retain
CFRelease(mainLoop);
// 加锁
__CFLock(&loopsLock);
}
// 从全局的字典 __CFRunLoops中获取对应于当前线程的RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 解锁
__CFUnlock(&loopsLock);
// 如果获取不到RunLoop就创建并存储
// 注意:在子线程中第一次获取RunLoop是获取不到的,获取不到的时候才去创建,这个if语句一般在子线程中第一次获取的时候才会执行
if (!loop) {
// 根据当前线程创建一个RunLoop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
// 加锁
__CFLock(&loopsLock);
// 再一次从全局的字典 __CFRunLoops中获取对应于当前线程的RunLoop
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 如果还是获取不到RunLoop,就存储当前线程和刚创建的RunLoop到全局字典 __CFRunLoops
if (!loop) {
// 存储当前线程和刚创建的RunLoop到全局字典 __CFRunLoops
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
// 加锁
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
// 解锁
__CFUnlock(&loopsLock);
// release局部的newLoop
CFRelease(newLoop);
}
// 判断t是否为当前线程,如果是就注册一个回调,当线程销毁的时候,也销毁其对应的RunLoop
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
// 返回当前线程对应的RunLoop
return loop;
}
上面源码的大概逻辑为:
RunLoop运行源码
上面讲解了获取当前RunLoop的原理,但是在RunLoop内部是如何执行任务的呢,总的逻辑如下图(图源RunLoop从源码到应用全面解析):
CFRunLoopRun和CFRunLoopRunSpecific源码
CFRunLoopRun和CFRunLoopRunSpecific源码,其中只对有助于理解的代码进行了注释:
// 供外部调用的公开的CFRunLoopRun方法,其内部会调用CFRunLoopRunSpecific
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
// 调用CFRunLoopRunSpecific
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 其内部会调用 __CFRunLoopRun 函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// 如果没找到 || 如果mode里没有source/timer/observer, 直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
// 取上一次运行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
// 如果本次mode和上次的mode一致
rl->_currentMode = currentMode;
// 初始化一个result为kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
// 通知observer:即将进入runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop逻辑
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
注意:
- 如果指定了一个不存在的mode来运行RunLoop,那么直接返回,mode不会被创建,所以这里传入的mode必须是存在的
- 如果指定了一个mode,但是这个mode中不包含任何modeItem,那么RunLoop也会直接返回,所以必须要传入至少包含一个modeItem的mode
- 在进入RunLoop之前通知observer,状态为kCFRunLoopEntry
- 在退出RunLoop之后通知observer,状态为kCFRunLoopExit
__CFRunLoopRun函数源码
因为__CFRunLoopRun函数过长,这里只写了有助于理解的代码:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//记录最后runloop状态,用于return
int32_t retVal = 0;
do {
//初始化一个存放内核消息的缓冲池
uint8_t msg_buffer[3 * 1024];
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
//取所有需要监听的port
__CFPortSet waitSet = rlm->_portSet;
//设置RunLoop为可以被唤醒状态
__CFRunLoopUnsetIgnoreWakeUps(rl);
//2.通知observer,即将触发timer回调,处理timer事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3.通知observer,即将触发Source0回调
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//执行加入当前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
//4.处理source0事件
//有事件处理返回true,没有事件返回false
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//执行加入当前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果没有Sources0事件处理 并且 没有超时,poll为false
//如果有Sources0事件处理 或者 超时,poll都为true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do-while循环不会走该分支,因为didDispatchPortLastTime初始化是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
//从缓冲区读取消息
msg = (mach_msg_header_t *)msg_buffer;
//5.接收dispatchPort端口的消息,(接收source1事件)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果接收到了消息的话,前往第9步开始处理msg
goto handle_msg;
}
}
didDispatchPortLastTime = false;
//6.通知观察者RunLoop即将进入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//设置RunLoop为休眠状态
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
//这里有个内循环,用于接收等待端口的消息
//进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop
do {
//7.接收waitSet端口的消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
//收到消息之后,livePort的值为msg->msgh_local_port,
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// 醒来
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
//取消runloop的休眠状态
__CFRunLoopUnsetSleeping(rl);
//8.通知观察者runloop被唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//9.处理收到的消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
//通过CFRunloopWake唤醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
//什么都不干,跳回2重新循环
// do nothing on Mac OS
}
//如果是定时器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1 处理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
//如果是定时器事件
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1处理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
//如果是dispatch到main queue的block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
//9.2执行block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
// 有source1事件待处理
if (rls) {
mach_msg_header_t *reply = NULL;
//9.2 处理source1事件
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
}
}
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
__CFRunLoopDoBlocks(rl, rlm);
// 根据之前的执行结果来决定怎么做,为retVal赋相应的值
if (sourceHandledThisLoop && stopAfterHandle) {
//进入RunLoop时传入的参数,处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
}else if (timeout_context->termTSR < mach_absolute_time()) {
//RunLoop超时
retVal = kCFRunLoopRunTimedOut;
}else if (__CFRunLoopIsStopped(rl)) {
//RunLoop被手动终止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
}else if (rlm->_stopped) {
//mode被终止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//mode中没有要处理的事件
retVal = kCFRunLoopRunFinished;
}
//除了上面这几种情况,都继续循环
} while (0 == retVal);
return retVal;
}
可以发现:
- 实际上 RunLoop 内部就是一个 do-while 循环
- 当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里,直到超时或被手动停止,该函数才会返回
- 同时RunLoop有很多个mode,但是RunLoop在run的时候必须只能指定其中一个mode,运行起来之后,被指定的mode即为currentMode
RunLoop和线程的关系(总结)
-
RunLoop保存在一个全局的Dictionary里面,线程为key,RunLoop为Value
-
线程刚创建的时候是没有RunLoop对象的,RunLoop会在第一次获取它的时候创建
-
RunLoop会在线程结束的时候销毁
-
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
-
每条线程都有唯一的一个与之对应的RunLoop对象
-
先有线程,再有RunLoop