不得不說禁熏,人的惰性是真可怕啊敏簿。
從上周六就到寫runLoop的建議開始,星期三告訴自己從星期四開始著手寫這篇博客洼冻。然而現(xiàn)在戳個時間戳崭歧,現(xiàn)在是4.30星期日。寫完發(fā)出去又不知道是什么時候啦撞牢,哈哈哈??
這一期講什么呢率碾?這一期講runLoop喲。一直以來屋彪,runLoop這個玄而又玄的東西似乎被當做了公司面試挑人的終極話題所宰,原因不難想,日常開發(fā)用到runLoop的地方少之又少
畜挥,沒有時間的積累這方面的知識應(yīng)該還是相對較于匱乏的仔粥,所以runLoop的了解側(cè)面也能發(fā)應(yīng)開發(fā)者的開發(fā)經(jīng)驗
,當然就被當做甄選人才的最后殺器蟹但。你可能一臉憤怒的說平時有用不到躯泰,我不會也不影響開發(fā)啊矮湘!的確斟冕,用不到口糕,但這只是一個過濾器而已缅阳。但是蛋疼的是,在于國內(nèi)的環(huán)境下,runLoop的相關(guān)資料又是少之又少十办,開發(fā)者又難以有一個深入的了解秀撇。
出于以上原因,老司機今天就以老司機個人的角度向族,盡可能將老司機所了解到的runLoop知識呵燕。
在今天的文章中你可能會看到以下內(nèi)容:
- runLoop相關(guān)知識
runLoop是什么
直譯以下,跑圈件相。翻譯以下再扭,事件循環(huán)
吧。
為什么要有這個事件循環(huán)呢夜矗?我們知道泛范,任何程序如果執(zhí)行到程序的最后一句之后都會結(jié)束運行。然而對于我們要的手機應(yīng)用程序而言紊撕,他顯然不可以執(zhí)行一個事件后就結(jié)束運行罢荡,他應(yīng)該具有持續(xù)接受事件
的能力從而不斷地處理事件。所以最基本的思路就是用于個while循環(huán)
讓程序不能走到最后一句結(jié)束对扶,而是在循環(huán)體內(nèi)不斷的接受事件区赵。所以我們需要runLoop。不過值得注意的是浪南,runLoop并不是iOS獨有的概念笼才,因為準去的來說runLoop應(yīng)該是一個模式,在其他平臺同樣存在這種模式
络凿,不過叫不叫runLoop我就不知道了患整。
runLoop是如何實現(xiàn)的
首先要明確的一點事,在平時我們使用的是Foundation框架的NSRunLoop類去做一些實現(xiàn)喷众,而其實NSRunLoop是基于CoreFoundation框架中的CFRunLoop進行的一層簡單的封裝各谚。所以我們這里著重介紹CFRunLoop,畢竟我們能拿到CFRunLoop的源碼到千。
1.runLoop的組成
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;
};
我們大概可以CFRunLoop是這么一個結(jié)構(gòu)體昌渤。
我們可以看到結(jié)構(gòu)體重有用來保證線程安全的鎖_lock
,有用來喚醒runLoop的端口_wakeUpPort
(這里后面會說到憔四,不用執(zhí)著)膀息,有線程對象_pthread
,還有一個模式集合_modes
以及一些其他輔助的屬性了赵。
1.1 _pthread
這里我要說的是潜支,runLoop與線程是一一對應(yīng)的。也就是說一個runLoop對應(yīng)著一個線程柿汛,一個線程對應(yīng)著一個runLoop冗酿。這里我們從runLoop的構(gòu)造函數(shù)和獲取函數(shù)即可看出:
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
CFRunLoopRef loop = NULL;
CFRunLoopModeRef rlm;
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
if (NULL == loop) {
return NULL;
}
(void)__CFRunLoopPushPerRunData(loop);
__CFRunLoopLockInit(&loop->_lock);
loop->_wakeUpPort = __CFPortAllocate();
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
__CFRunLoopSetIgnoreWakeUps(loop);
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
loop->_commonModeItems = NULL;
loop->_currentMode = NULL;
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
loop->_blocks_head = NULL;
loop->_blocks_tail = NULL;
loop->_counterpart = NULL;
loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
loop->_winthread = 0;
#endif
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
return loop;
}
可以看出構(gòu)造一個runLoop對象僅需要一個pthread_t線程即可。即一個runLoop對應(yīng)一個線程。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {//如果傳入線程為空指針則默認取主線程對應(yīng)的runLoop
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {//__CFRunLoops就是一個全局字典裁替,以下代碼為如果全局字典不存在則創(chuàng)建全局字典项玛,并將主線程對應(yīng)的mainLoop存入字典中
__CFSpinUnlock(&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);
__CFSpinLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));//從全局字典中,取出對應(yīng)線程的runLoop
__CFSpinUnlock(&loopsLock);
if (!loop) {//若對應(yīng)線程的runLoop為空弱判,則創(chuàng)建對應(yīng)相乘的runLoop并保存在全局字典中
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&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
__CFSpinUnlock(&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的獲取函數(shù)襟沮,我們看到系統(tǒng)從一個全局字典中取出runLoop,key就是一個線程昌腰,這足以說明runLoop與線程是一一對應(yīng)
的關(guān)系开伏。
值得一提的是,一個線程最開始是沒有對應(yīng)的runLoop
的遭商,是在調(diào)用獲取函數(shù)的時候才對應(yīng)了一個runLoop的
硅则。因為本身這個對應(yīng)關(guān)系是有runLoop類管理的,而不是線程株婴。
當然上述兩個為私有api怎虫,CF真正對外暴露的只有兩個接口:
CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);
兩個方法的實現(xiàn)很簡單,只要把對應(yīng)的線程傳入獲取函數(shù)即可:
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());
}
1.2 _modes
我們看到困介,一個runLoop中同時還維護著一個集合大审,_modes。那么這個modes是做什么的呢座哩?應(yīng)該說徒扶,_modes才是runLoop的核心
「睿咳咳(敲黑板)姜骡,劃重點了啊。
首先我們看一下這個_modes里面到底都裝了些什么屿良?
答案是__CFRunLoopMode
對象圈澈。那么他又是什么呢?
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 */
};
這里老司機挑出了幾個重點尘惧,有用來標志runLoopMode的標志_name
康栈,有兩個事件源的集合_sources0、_sources1
喷橙,有一組觀察者_obeserver
啥么,有一組被加入到runLoop中的_timers
,還有Mode本身維護著的一個用于計時的_timerSource
贰逾,_timerPort
悬荣。這兩個一個是GCD時鐘一個是內(nèi)核時鐘。
至于runLoopMode為什么長這樣疙剑,老司機會在下面runLoopRun的實現(xiàn)中結(jié)合代碼講到氯迂。
2.runLoop代碼實現(xiàn)
恩践叠,接下來代碼有點長,先給你們看一下大概流程囚戚,然后對著流程去看一下代碼。
前方高能預(yù)警轧简,代碼很多驰坊!
runLoop核心代碼
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();//獲取當前內(nèi)核時間
if (__CFRunLoopIsStopped(rl)) {//如果當前runLoop或者runLoopMode為停止狀態(tài)的話直接返回
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//判斷是否是第一次在主線程中啟動RunLoop,如果是且當前RunLoop為主線程的RunLoop,那么就給分發(fā)一個隊列調(diào)度端口
mach_port_name_t dispatchPort = MACH_PORT_NULL;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//給當前模式分發(fā)隊列端口
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
//初始化一個GCD計時器哮独,用于管理當前模式的超時
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
// 第一步拳芙,進入循環(huán)
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
__CFPortSet waitSet = rlm->_portSet;
//設(shè)置當前循環(huán)監(jiān)聽端口的喚醒
__CFRunLoopUnsetIgnoreWakeUps(rl);
// 第二步,通知觀察者準備開始處理Timer源事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 第三步皮璧,通知觀察者準備開始處理Source源事件
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//執(zhí)行提交到runLoop中的block
__CFRunLoopDoBlocks(rl, rlm);
// 第四步舟扎,執(zhí)行source0中的源事件
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//如果當前source0源事件處理完成后執(zhí)行提交到runLoop中的block
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
//標志是否等待端口喚醒
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 第五步,檢測端口悴务,如果端口有事件則跳轉(zhuǎn)至handle_msg(首次執(zhí)行不會進入判斷睹限,因為didDispatchPortLastTime為true)
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
// 第六步,通知觀察者線程進入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 標志當前runLoop為休眠狀態(tài)
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
// 第七步讯檐,進入循環(huán)開始不斷的讀取端口信息羡疗,如果端口有喚醒信息則喚醒當前runLoop
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
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);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
//標志當前runLoop為喚醒狀態(tài)
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 第八步,通知觀察者線程被喚醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//執(zhí)行端口的事件
handle_msg:;
//設(shè)置此時runLoop忽略端口喚醒(保證線程安全)
__CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
// 第九步别洪,處理端口事件
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {//處理定時器事件
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
//處理有GCD提交到主線程喚醒的事件
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
//處理source1喚醒的事件
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
// 處理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);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
//返回對應(yīng)的返回值并跳出循環(huán)
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)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
// 第十步叨恨,釋放定時器
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
這個方法有點有點長,300行代碼=挖垛。=
這300行的流程其實就是上面歸納的10步:
首先進入runLoop對應(yīng)的Mode并開始循環(huán)痒钝,然后在休眠之前做了三件事:DoBlocks、DoSource0痢毒、檢測source1端口是否有消息送矩,如果有則跳過稍后的休眠。
然后runLoop就進入了休眠狀態(tài)哪替,直到有端口事件喚醒runLoop益愈,被喚醒后則處理響應(yīng)的端口事件然后再次開始循環(huán)。直到runLoop超時或者runLoop被停止后在結(jié)束runLoop夷家。
不過好在代碼很全蒸其,在這里我們能出到很多問題。
1.source0库快,source1
首先這個源事件分為兩種摸袁,一種是不基于端口的source0,一直是基于端口的source1义屏。
Source0 只包含了一個回調(diào)(函數(shù)指針)靠汁,它并不能主動觸發(fā)事件蜂大。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source)蝶怔,將這個 Source 標記為待處理奶浦,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件踢星。
Source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針)澳叉,被用于通過內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動喚醒 RunLoop 的線程沐悦,其原理在下面會講到成洗。
————引自深入理解RunLoop
source0呢主要處理App內(nèi)部事件、App自己負責管理(觸發(fā))藏否,如UIEvent瓶殃、CFSocket
source1呢主要有Runloop和內(nèi)核管理,Mach port驅(qū)動副签,如CFMahPort遥椿、CFMessagePort
————引自孫源runLoop線下分享會視頻
2.NSTimer事件是借助runLoop實現(xiàn)的。
這點老司機早在CoreAnimation系列中第三篇介紹三個Timer的時候老司機就有提到過淆储,在初始化Timer的時候要將Timer提交到runLoop中修壕,并且要指定mode,才可以工作遏考。今天我們可以深入講一下慈鸠。
這個事件是怎么執(zhí)行的?并且為什么有的時候會延遲灌具?為什么子線程中創(chuàng)建的Timer并不執(zhí)行青团?
首先,在進入循環(huán)開始以后咖楣,就要處理source0事件督笆,處理后檢測一下source1端口是否有消息,如果一個Timer的時間間隔剛好到了則此處有可能會得到一個消息诱贿,則runLoop直接跳轉(zhuǎn)至端口激活處從而去處理Timer事件娃肿。
第二,為什么會延遲珠十?我們知道料扰,兩次端口事件是在兩個runLoop循環(huán)中分別執(zhí)行的。比如Timer的時間間隔為1秒焙蹭,在第一次Timer回調(diào)結(jié)束后晒杈,在很短時間內(nèi)立即進入runLoop的下一次循環(huán),這次并不是Timer回調(diào)并且是一個計算量非常大的任務(wù)孔厉,計算時間超過了1秒拯钻,那么runLoop的第二個循環(huán)就要執(zhí)行很久帖努,無法進入下一個循環(huán)等待有可能即將到來的Timer第二次回調(diào)的信號,所以Timer第二次回調(diào)就會推遲了粪般。
第三拼余,為什么在子線程中創(chuàng)建的Timer并且提交到當前runLoop中并不會運行?這還是要從runLoop的獲取函數(shù)中看亩歹,當調(diào)用currentRunLoop的時候會取當前線程對應(yīng)的runLoop匙监,而首次是取不到的,則會創(chuàng)建一個新的runLoop捆憎。但是舅柜!這個runLoop并沒有run梭纹。就是沒有開啟=躲惰。=
3.同一時間內(nèi),runLoop只能運行同一種mode变抽。那commonMode是怎么實現(xiàn)的础拨?
從runLoop的結(jié)構(gòu)我們可以知道,一個runLoop會包含多種runLoopMode绍载,runLoop是不停的在這些mode之間進行切換去完成對應(yīng)Mode中的相關(guān)任務(wù)诡宗。
首先為什么說runLoop只能在各種Mode之間切換,同一時間只能存在一個呢击儡?
因為上面那個方法必須要傳一個runLoopMode塔沃,然后這個方法貫穿始終,都在用阳谍。
我們看到蛀柴,上面的方法中首先就要傳入一個指定的mode才能執(zhí)行對應(yīng)mode中的事件。那么所謂的CommonMode是如何實現(xiàn)的呢矫夯?
我們看到runLoop中執(zhí)行任務(wù)有調(diào)到CFRunLoopDoBlocks這么一個函數(shù)鸽疾,那么這個函數(shù)是什么樣的呢?
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
if (!rl->_blocks_head) return false;
if (!rlm || !rlm->_name) return false;
...省略一些非重點...
while (item) {
struct _block_item *curr = item;
item = item->_next;
Boolean doit = false;
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
} else {
doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
}
if (!doit) prev = curr;
if (doit) {
if (prev) prev->_next = item;
if (curr == head) head = item;
if (curr == tail) tail = prev;
void (^block)(void) = curr->_block;
CFRelease(curr->_mode);
free(curr);
if (doit) {
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
did = true;
}
...省略一些非重點...
return did;
}
我們看到doit
這個bool變量完全決定了當前block是否執(zhí)行训貌。默認他是No的制肮,而他被置為true的條件就是CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode))
。就是當前mode與制定mode相等或者當前mode為commonMode(此處為一個字符串)且commonMode(此處為一個集合递沪,若有不懂豺鼻,請看runLoop結(jié)構(gòu))這個集合中包含指定mode。
這是因為這個判斷的存在才允許commondMode可以在任意Mode下執(zhí)行款慨。
當然這是提交到runLoop里的代碼塊才會走到__CFRunLoopDoBlocks
這個方法拘领。
相同的,我們通過上述代碼也可以知道樱调,runLoop通過端口喚醒的事件需要通過__CFRunLoopDoSource1和__CFRunLoopDoTimers兩個方法來調(diào)用约素。__CFRunLoopDoSource1方法沒什么說的届良,直接調(diào)用源事件runLoopSourceRef即可。重點我們看一下Timer的實現(xiàn)圣猎,核心代碼如下:
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */
Boolean timerHandled = false;
CFMutableArrayRef timers = NULL;
//遍歷runLoopMode維護的Timers數(shù)組士葫,取其中有效的timer并加入新臨時數(shù)組
for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
if (rlt->_fireTSR <= limitTSR) {
if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(timers, rlt);
}
}
}
//遍歷臨時數(shù)組,每個有效Timer調(diào)用__CFRunLoopDoTimer
for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
timerHandled = timerHandled || did;
}
if (timers) CFRelease(timers);
return timerHandled;
}
我們可以看到送悔,此處Timer是否會回調(diào)完全取決于對應(yīng)Mode的_Timers數(shù)組慢显。那么當我們將Timer加入到commonModes中的時候一定是同時將Timer加入到了commonModes所包含的其他Mode中了,我們看下代碼:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {//commonModes分支
//取到commonModes所代表的Mode的集合
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
//將commonModeItems中加入當前定時器
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
//最主要還是還是這句欠啤,這句的作用是集合中的所有對象均調(diào)用__CFRunLoopAddItemToCommonModes這個方法荚藻。
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {//非commonModes的分支
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
if (NULL != rlm) {
if (NULL == rlm->_timers) {
CFArrayCallBacks cb = kCFTypeArrayCallBacks;
cb.equal = NULL;
rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
}
}
if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
__CFRunLoopTimerLock(rlt);
if (NULL == rlt->_runLoop) {
rlt->_runLoop = rl;
} else if (rl != rlt->_runLoop) {
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
return;
}
CFSetAddValue(rlt->_rlModes, rlm->_name);
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
__CFRepositionTimerInMode(rlm, rlt, false);
__CFRunLoopTimerFireTSRUnlock();
if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
// Normally we don't do this on behalf of clients, but for
// backwards compatibility due to the change in timer handling...
if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
}
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
}
}
我們可以看到,當加入到commonModes中時洁段,實際上系統(tǒng)是找出commonModes代表的所有Mode
应狱,如defaultMode和trackingMode,讓后分別將其加入了這些mode中祠丝。
同樣的方法還有CFRunLoopAddSource
/CFRunLoopAddObserver
都是同樣的道理懂讯。
所以說當scrollView或其子類進行滾動的時候嘲驾,UIKIT會自動將當前runLoopMode切換為UITrackingRunLoopMode姨丈,所以你加在defaultMode中的計時器當然不會走了九昧。
4.runLoop是如何休眠有如何被喚醒的?
從第7步開始叠蝇,我們看到runLoop進入了休眠狀態(tài)璃岳。然而所謂的休眠狀態(tài)指示將當前runLoop標記為休眠之后,進入了一個while死循環(huán)悔捶。然后在循環(huán)內(nèi)就不斷的去讀取端口消息铃慷。如果說從端口中讀取到一個喚醒信息的話,break掉while循環(huán)從而進入喚醒狀態(tài)炎功。
關(guān)于runLoop的幾種mode老司機之前也有講過枚冗,在CoreAnimation中的第三篇中有講到,這里就只羅列一下蛇损。
- NSDefaultRunLoopMode
- NSConnectionReplyMode
- NSModalPanelRunLoopMode
- UITrackingRunLoopMode
- NSRunLoopCommonModes
5.可以喚醒runLoop的都有哪些事件赁温?
從源碼中我們可以看出,所謂的runLoop進入休眠狀態(tài)不過是一個while循環(huán)淤齐,如下:
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
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);
相應(yīng)的我們還得看一個函數(shù)股囊,__CFRunLoopServiceMachPort
:
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t**buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... */
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
ret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
我們先看后面這個函數(shù),在這里僅有兩種情況會對livePort進行賦值
更啄,一種是成功獲取到消息后稚疹,會根據(jù)情況賦值為msg->msgh_local_port或者MACH_PORT_NULL,而另一種獲取消息超時的情況會賦值為MACH_PORT_NULL祭务。首先請先記住這兩個結(jié)論内狗。
然后我們把目光聚焦到while循環(huán)中怪嫌,在調(diào)用__CFRunLoopServiceMachPort
后如果livePort變成了modeQueuePort
(livePort初值為MACH_PORT_NULL),則代表為當前隊列的檢測端口柳沙,那么在_dispatch_runloop_root_queue_perform_4CF
的條件下再次進入二級循環(huán)岩灭,知道Timer被激活了才跳出二級循環(huán)繼續(xù)循環(huán)一級循環(huán)。(這一步的目的不好意思老司機真沒看懂)赂鲤。
那么如果livePort不為modeQueuePort時我們的runLoop被喚醒噪径。這代表__CFRunLoopServiceMachPort給出的livePort只有兩種可能:一種情況為MACH_PORT_NULL,另一種為真正獲取的消息的端口
数初。
所以我們可以看到后面runLoop處理端口時間的方法如下的判斷:
if (MACH_PORT_NULL == livePort) {//什么都不做找爱,有肯能是超時之類的或者是信息過大
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {//只有外界調(diào)用CFRunLoopWakeUp才會進入此分支,這是外部主動喚醒runLoop的接口
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {//這里不是從runLoop休眠后喚醒到這里的泡孩,而是在runLoop10步中的第五步跳轉(zhuǎn)過來的车摄,是處理計時器事件
CFRUNLOOP_WAKEUP_FOR_TIMER();
...省略處理計時器事件的代碼...
}
#endif
else if (livePort == dispatchPort) {//這里是處理GCD提交到mainQueue的block的端口事件
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
...省略處理GCD的代碼...
} else {//之前所有情況都不是,那么喚醒runLoop的就只可能是source1的源事件了珍德。
CFRUNLOOP_WAKEUP_FOR_SOURCE();
...省略處理source1源事件的代碼...
}
}
runLoop的喚醒過程练般,及喚醒過后的時間處理就是上面的流程
矗漾,大家可以看看每個分支后的注釋锈候。同時runLoopRun的核心代碼也就解讀完畢了。
剩下的幾個run方法事實上都是對這個核心方法的封裝了老司機不都說了:
- CFRunLoopRunSpecific
- CFRunLoopRun
- CFRunLoopRunInMode
至此敞贡,整個runLoop中的核心流程老司機也算帶著大家分析了一遍~
runLoop都能做什么
說了這么多泵琳,那么runLoop都能做些什么呢?
以下內(nèi)容整理自深入理解RunLoop
、孫源runLoop線下分享會視頻:
AutoReleasePool:
App啟動后誊役,蘋果在主線程 RunLoop 里注冊了兩個 Observer获列,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一個 Observer 監(jiān)視的事件是 Entry(即將進入Loop)蛔垢,其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池击孩。其 order 是-2147483647,優(yōu)先級最高鹏漆,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前巩梢。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準備進入休眠) 時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池艺玲。這個 Observer 的 order 是 2147483647括蝠,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后饭聚。
在主線程執(zhí)行的代碼忌警,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的秒梳。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著法绵,所以不會出現(xiàn)內(nèi)存泄漏箕速,開發(fā)者也不必顯示創(chuàng)建 Pool 了。
CAAnimation
我們知道CAAniamtion為我們提供的是補間動畫
朋譬,開發(fā)者只要給出始末狀態(tài)后中間狀態(tài)有系統(tǒng)自動生成弧满。那么動畫是怎么出現(xiàn)的呢,是開發(fā)者給出始末狀態(tài)后此熬,系統(tǒng)計算出每一個中間態(tài)的各項參數(shù)
庭呜,然后啟一個定時器不斷去回調(diào)并改變屬性
。
事件響應(yīng)
蘋果注冊了一個 Source1 (基于 mach port 的) 用來接收系統(tǒng)事件犀忱,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()募谎。
當一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收阴汇。這個過程的詳細情況可以參考這里数冬。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸搀庶,加速拐纱,接近傳感器等幾種 Event,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進程哥倔。隨后蘋果注冊的那個 Source1 就會觸發(fā)回調(diào)秸架,并調(diào)用 _UIApplicationHandleEventQueue() 進行應(yīng)用內(nèi)部的分發(fā)。
_UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理并包裝成 UIEvent 進行處理或分發(fā)咆蒿,其中包括識別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等东抹。通常事件比如 UIButton 點擊、touchesBegin/Move/End/Cancel 事件都是在這個回調(diào)中完成的沃测。
手勢識別
當上面的 _UIApplicationHandleEventQueue() 識別了一個手勢時缭黔,其首先會調(diào)用 Cancel 將當前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對應(yīng)的 UIGestureRecognizer 標記為待處理蒂破。
蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進入休眠) 事件馏谨,這個Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會獲取所有剛被標記為待處理的 GestureRecognizer附迷,并執(zhí)行GestureRecognizer的回調(diào)惧互。
當有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時,這個回調(diào)都會進行相應(yīng)處理挟秤。
定時器
不多說了這個就壹哺。
PerformSelecter
當調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當前線程的 RunLoop 中艘刚。所以如果當前線程沒有 RunLoop管宵,則這個方法會失效。
當調(diào)用 performSelector:onThread: 時,實際上其會創(chuàng)建一個 Timer 加到對應(yīng)的線程去箩朴,同樣的岗喉,如果對應(yīng)線程沒有 RunLoop 該方法也會失效。
基本也就差不多了炸庞。
老司機寫這篇博客呢钱床,也是應(yīng)盆友的要求寫的
,基本上也是針對前人博客的總結(jié)
以及自己對源碼的解讀
埠居。畢竟有了源碼以后runLoop也就沒有那么神秘了查牌。只是希望大家明白runLoop并不是什么多么可怕的東西,只要我們一點一點去看滥壕,他也是人寫的代碼啊=纸颜。=不過老司機懶到連偽代碼都沒給你們寫直接上的源碼。原諒一個懶癌晚期的人吧
绎橘。
還是要感謝兩位大神郭耀源和孫源兩位大神之前的博客和視頻講解讓我很受用胁孙。大神名里都帶源字,我要不要改成老源
=称鳞。=
參考資料:
另外你如果想看孫源的視頻涮较,老司機已經(jīng)給了下載鏈接,然后從最開始到1小時10分鐘的時候都是干貨冈止,后面35分鐘偏討論狂票,時間緊的童靴可以跳過,但是后面也有很好的思路分享靶瘸,聽聽也是不錯的苫亦。
如果你想看runLoop源碼毛肋,因為源碼里面有4000多行怨咪,老司機讀源碼的時候講有用的方法的行數(shù)都記了下來,你可以對應(yīng)的找一下:
方法名 | 行數(shù) |
---|---|
__CFRunLoopFindMode | #754 |
__CFRunLoopCreate | #1321 |
_CFRunLoopGet0 | #1358 |
__CFRunLoopAddItemToCommonModes | #1536 |
__CFRunLoopDoObservers | #1668 |
__CFRunLoopDoSources0 | #1764 |
__CFRunLoopDoSource1 | #1829 |
__CFRepositionTimerInMode | #1999 |
__CFRunLoopDoTimer | #2024 |
__CFRunLoopDoTimers | #2152 |
__CFRunLoopServiceMachPort | #2196 |
__CFRunLoopRun | #2308 |
CFRunLoopRunSpecific | #2601 |
CFRunLoopRun | #2628 |
CFRunLoopRunInMode | #2636 |
CFRunLoopWakeUp | #2645 |
CFRunLoopAddSource | #2791 |
CFRunLoopAddObserver | #2978 |
CFRunLoopAddTimer | #3081 |
打完收功润匙!
老司機寫這篇博客真的事廢了很多心血的诗眨,好就給贊關(guān)注吧么么噠??
無恥的廣告時間:
DWCoreTextLabel支持cocoaPods
了~
pod search DWCoreTextLabel
插入圖片、繪制圖片孕讳、添加事件統(tǒng)統(tǒng)一句話實現(xiàn)~
盡可能保持系統(tǒng)Label屬性讓你可以無縫過渡使用~
恩匠楚,說了這么多,老司機放一下地址:DWCoreTextLabel厂财,寶寶們給個star吧愛你喲