RunLoop 是iOS中重要的一个概念,在 iOS 里它是由 CFRunLoop 实现。本章将从源码的方面梳理下RunLoop相关的概念、结构、原理。
浅谈RunLoop
RunLoop概念:
一般来讲,一个线程在执行任务完成后,就会结束。但是在我们的应用程序中不会直接结束,而是会在线程中构建一个消息循环机制。当有事件要去处理时保活线程,当没有事件要处理时让线程进入休眠,这个消息循环的机制就是RunLoop。
RunLoop和线程的关系:
- RunLoop是基于线程来管理的,所以是一一对应的关系
- 主线程中会自动获取Runloop,子线程默认不会获取Runloop
RunLoop创建:
苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain()
和 CFRunLoopGetCurrent()
,这两个函数最终会调用私有函数_CFRunLoopGet0()
。
相关源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| static CFMutableDictionaryRef __CFRunLoops = NULL; 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) { if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); if (!__CFRunLoops) { __CFUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (!loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }
|
RunLoop结构
RunLoop的构成由以下几个定义的对象组成:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopObserverRef
- CFRunLoopTimerRef
1 2 3 4 5 6 7 8 9 10
| // CFRunLoopModeRef typedef struct __CFRunLoopMode *CFRunLoopModeRef; // CFRunLoopRef typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef; // CFRunLoopSourceRef typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef; // CFRunLoopObserverRef typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef; // CFRunLoopTimerRef typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
|
- CFRunLoopRef
CFRunLoopRef
包含了对应的线程,Mode集合,当前的Mode等等;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 结构体定义 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; CFAbsoluteTime _runTime; CFAbsoluteTime _sleepTime; CFTypeRef _counterpart; };
|
- CFRunLoopModeRef
CFRunLoopModeRef
代表着RunLoop的运行模式,一个RunLoop中可以有多个mode,一个mode里面又可以有多个source、observer、timer等等。系统五个Mode:
kCFRunLoopDefaultMode
:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode
:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode
: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode
: 接受系统事件的内部 Mode,通常用不到
kCFRunLoopCommonModes
: 这是一个占位用的Mode,不是一种真正的Mode1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| // 结构体定义 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 */ };
|
- CFRunLoopSourceRef
CFRunLoopSourceRef
是产生事件的地方。Source有两个版本:Source0 和 Source1。
1 2 3 4 5 6 7 8 9 10 11 12
| // 结构体定义 struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; pthread_mutex_t _lock; CFIndex _order; /* immutable */ CFMutableBagRef _runLoops; union { CFRunLoopSourceContext version0; /* immutable, except invalidation */ CFRunLoopSourceContext1 version1; /* immutable, except invalidation */ } _context; };
|
Source0于Source1的区别如下:
- Source0 只包含了一个回调 ,它并不能主动触发事件
- Source1包含了一个 mach_port和一个回调 ,被用于通过内核和其他线程相互发送消息(系统消息,例如触摸手势,屏幕解锁,按键等等)这种Source 能主动唤醒RunLoop 的线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| // Source0 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, CFRunLoopMode mode); void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode); void (*perform)(void *info); } CFRunLoopSourceContext;
// Source1 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_OSX || TARGET_OS_IPHONE mach_port_t (*getPort)(void *info); void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info); #else void * (*getPort)(void *info); void (*perform)(void *info); #endif } CFRunLoopSourceContext1;
|
- CFRunLoopObserverRef
CFRunLoopObserverRef
即是RunLoop的Observer,在它的结构体内部有一个_runLoop
成员和回调,每个Observer只能监听一个RunLoop,当 RunLoop 的状态发生变化时,Observer就会被触发回调。RunLoop的六个状态如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| // 结构体定义 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 */ };
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 进入 loop kCFRunLoopBeforeTimers = (1UL << 1), // 触发 Timer 回调 kCFRunLoopBeforeSources = (1UL << 2), // 触发 Source0 回调 kCFRunLoopBeforeWaiting = (1UL << 5), // 等待 mach_port 消息 kCFRunLoopAfterWaiting = (1UL << 6), // 接收 mach_port 消息 kCFRunLoopExit = (1UL << 7), // 退出 loop kCFRunLoopAllActivities = 0x0FFFFFFFU // loop 所有状态改变 };
|
- CFRunLoopTimerRef
CFRunLoopTimerRef
是基于Timer的触发器,在它的结构体内部有一个时间间隔和回调,一个Timer仅能够添加到一个RunLoop中,当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间间隔到达后,RunLoop
会被唤醒以执行该回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct __CFRunLoopTimer { CFRuntimeBase _base; uint16_t _bits; pthread_mutex_t _lock; CFRunLoopRef _runLoop; CFMutableSetRef _rlModes; CFAbsoluteTime _nextFireDate; CFTimeInterval _interval; /* immutable */ CFTimeInterval _tolerance; /* mutable */ uint64_t _fireTSR; /* TSR units */ CFIndex _order; /* immutable */ CFRunLoopTimerCallBack _callout; /* immutable */ CFRunLoopTimerContext _context; /* immutable, except invalidation */ };
|
以上这些就是对RunLoop结构的介绍,下面我将结合CFRunLoop 的源码 来跟分析下 RunLoop 的原理吧。
RunLoop实现原理
首先,我们熟悉下源码中的主流程函数调用顺序:
CFRunLoopRun()
->
CFRunLoopRunSpecific()
->
__CFRunLoopRun()
下面是基于源码精简的流程:
1 2 3 4 5 6 7 8
| // CFRunLoopRun void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| // CFRunLoopRunSpecific NSInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); 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); CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode; int32_t result = kCFRunLoopRunFinished;
// 通知 observers 状态发生变化,进入loop if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // RunLoop里面具体要做的事情, 主要是一个循环 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; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| // __CFRunLoopRun static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); int32_t retVal = 0; do { ...
// 通知 Observers RunLoop 状态发生变化 触发 Timer 回调 if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 通知 Observers RunLoop 状态发生变化 触发 Source0 回调 if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 接着执行加入的 block回调 __CFRunLoopDoBlocks(rl, rlm); // 接着触发 Source0 回调 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); // 可能会再次执行加入的 block回调 if (sourceHandledThisLoop) { __CFRunLoopDoBlocks(rl, rlm); } // 接着如果有 Source1 是 ready 状态的话,就会跳转到 handle_msg 去处理消息 if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { msg = (mach_msg_header_t *)msg_buffer; if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { goto handle_msg; }
}
...
// if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
...
// 等待唤醒 do { __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); // 基于port的source事件,调用者唤醒 if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue)); // Timer 时间到、RunLoop 超时 if (rlm->_timerFired) { rlm->_timerFired = false; break; } else { if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); } } else { break; } } while (1);
... // 接收到 mach_port 消息, RunLoop 的线程被唤醒了 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); handle_msg:; ... // 接着开始处理消息 // 如果 Timer 时间到,就触发 Timer 回调 if (msg-is-timer) { if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer, because we apparently fired early __CFArmNextTimerInMode(rlm, rl); } } // 如果 dispatch 就执行 block else if (msg_is_dispatch) { __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } // Source1 事件的话,就处理这个事件 else { CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); if (sourceHandledThisLoop) { mach_msg(reply, MACH_SEND_MSG, reply); } } ... // 执行加入的 block回调 __CFRunLoopDoBlocks(rl, rlm); // loop 已经走完,这里要根据RunLoop 的状态来判断是否需要走下一个 loop, if (sourceHandledThisLoop && stopAfterHandle) { // 事件已处理完 retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { // 设置了超时时间,超时返回 retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) { // 外部调用者强制停止 __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { // 停止状态 rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { // mode 为空,RunLoop 结束 retVal = kCFRunLoopRunFinished; }
} while (0 == retVal);
return retVal; }
|
第一步:外层函数执行了do while,为线程后面loop做准备。 通知observers,RunLoop状态变化:kCFRunLoopEntry
,RunLoop 要开始进入循环了;
注意: 看这里的while条件(kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result)
第二步:开启一个 do while 来保活线程,通知 Observers,RunLoop状态变化: kCFRunLoopBeforeTimers
, 处理 Timer 回调
第三步:通知 Observers,RunLoop状态变化: kCFRunLoopBeforeSources
,处理 Source0 回调
第四步:然后执行加入RunLoop的 block回调,接着会触发 Source0 回调,接着可能会再次执行加入的 block回调,如果有 Source1 是 ready
状态的话,就会跳转到 handle_msg 去处理消息代码如下:
第五步:通知 Observers,RunLoop状态变化:kCFRunLoopBeforeWaiting
,线程将进入休眠(sleep)状态
第六步:进入休眠后,会等待 mach_port 的消息再次唤醒
第七步:通知 Observer,RunLoop状态变化:kCFRunLoopAfterWaiting
,接收到 mach_port 消息,RunLoop 的线程被唤醒了
第八步:RunLoop 被唤醒后开始处理消息了:
- 如果是 Timer 时间到了,触发 Timer 的回调
- 如果是 dispatch ,执行 block回调
- 如果是 source1 事件,处理这个事件回调
第九步:执行加入的 block回调
第十步:根据RunLoop的状态来判断是否需要走下一个 loop,结束循环 或者 回到外层循环进入第二步
Runloop具体流程可以参考以下这个图
写在最后
看完了这篇文章,那什么是RunLoop?它是如何处理事务的呢?
RunLoop其实就是一种事件循环机制,通过这个事件循环机制,当有事件要去处理时保活线程,当没有事件要处理时让线程进入休眠;
RunLoop与线程是什么关系?
RunLoop与线程是一对一的关系,一个RunLoop中之对应一个线程,主线程中会自动获取Runloop,子线程默认不会获取Runloop
如何实现一个常驻线程?
创建RunLoop,添加port/source等事件维护RunLoop的事件循环,启动RunLoop (最简单的做法就是创建一个NSTimer,加入到子线程的RunLoop中,然后启动RunLoop)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| //创建Source事件 CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL}; CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); //创建RunLoop,同时向RunLoop的DefaultMode下加入source CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); while (run) { @autoreleasepool { //令当前的RunLoop运行在DefaultMode下 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true); } } //在某个情况下 静态变量run = NO时,可以保证跳出RunLoop,线程退出 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); CFRelease(source);
|
在了解了RunLoop原理之后,越来越多的人都开始从RunLoop下手,来监控卡顿问题。这其实也是因为线程的事件依赖于RunLoop,而通过监听RunLoop的状态,我们就可以发现调用方法是否执行时间过长,从而判断出是否出现了卡顿。
RunLoop源码