RunLoop,從字面意思看叫做運(yùn)行循環(huán)另假。RunLoop基本作用:
- 保持程序的持續(xù)運(yùn)行
- 處理APP中的各種事件(觸摸事件像屋,定時(shí)器事件...)
- 節(jié)省CPU資源,提高程序性能:有事做事边篮,沒事休息
等等......
RunLoop內(nèi)部其實(shí)就是一個(gè)do-while循環(huán)己莺,在這個(gè)循環(huán)內(nèi)部不斷的處理各種事件,因此保證了程序不會(huì)退出一直運(yùn)行戈轿;
程序的main函數(shù)中凌受,UIApplicationMain函數(shù)內(nèi)部自動(dòng)啟動(dòng)了一個(gè)RunLoop,所以UIApplicationMain一直沒有返回思杯,保持程序持續(xù)運(yùn)行胜蛉。這個(gè)默認(rèn)啟動(dòng)的RunLoop是跟主線程相關(guān)聯(lián)的;
RunLoop對(duì)象:
iOS中有2套API訪問和使用RunLoop:
- Foundation框架下的:NSRunLoop
- CoreFoundation框架下的:CFRunLoopRef;
CFRunLoopRef和NSRunLoop都代表RunLoop對(duì)象誊册;NSRunLoop是基于CFRunLoopRef的一層OC包裝奈梳,所以要了解RunLoop內(nèi)部結(jié)構(gòu),需要多研究CFRunLoopRef層的API解虱;
獲取RunLoop對(duì)象
// Foundation:
[NSRunLoop currentRunLoop];// 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
//Core Foundation:
CFRunLoopGetCurrent();// 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain();// 獲得主線程的RunLoop對(duì)象
獲取RunLoop的源碼如下
// 存放Runloop的全局變量__CFRunLoops,key是pthread_t漆撞,value是Runloop
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// 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為0或?yàn)榭毡硎局骶€程
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
//__CFRunLoops為空殴泰,則初始化__CFRunLoops,并創(chuàng)建主線程的Runloop浮驳,保存到__CFRunLoops中
if (!__CFRunLoops) {
__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));
__CFSpinUnlock(&loopsLock);
// 通過pthread_t獲取loop悍汛,如果沒獲取到,則新建一個(gè)loop保存到__CFRunLoops中至会,返回該loop
if (!loop) {
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;
}
可參考蘋果官方文檔:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
蘋果開源代碼:
http://opensource.apple.com/source/CF/
CFRunLoopRef
源碼是這樣定義CFRunLoopRef的:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
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; // runloop對(duì)應(yīng)的線程
uint32_t _winthread;
CFMutableSetRef _commonModes; //被標(biāo)記為commonMode的模式
CFMutableSetRef _commonModeItems;//添加到commonMode的item
CFRunLoopModeRef _currentMode; //Runloop當(dāng)前的運(yùn)行的模式
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
從代碼中可以看出Runloop主要包含線程pthread_t和模式CFRunLoopModeRef离咐。_commonModes存放已被標(biāo)記為“common”的mode。主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode,這兩個(gè) Mode 都已經(jīng)被標(biāo)記為”Common”屬性奉件。_commonModeItem是哪些被添加到commonMode的source/timer/observer宵蛀。
RunLoop與線程的關(guān)系
- 每條線程都有唯一一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象;
- Runloop保存在一個(gè)全局的dictionary中县貌,線程為key术陶,Runloop為value;
- 主線程的RunLoop程序啟動(dòng)后自動(dòng)創(chuàng)建好了煤痕,而子線程的RunLoop在第一次獲取時(shí)創(chuàng)建梧宫;
- RunLoop在線程結(jié)束時(shí)銷毀;
RunLoop與RunLoopMode的關(guān)系
- RunLoopMode代表RunLoop的運(yùn)行模式摆碉,每個(gè)RunLoop包含若干個(gè)Mode
- 每次RunLoop啟動(dòng)時(shí)塘匣,只能指定其中一個(gè)Mode,這個(gè)Mode稱作CurrentMode巷帝,Runloop循環(huán)處理CurrentMode的所有事件忌卤;
- 如果需要切換Mode,只能退出Loop锅睛,再重新指定一個(gè)Mode進(jìn)入埠巨;這樣可以分隔開不同mode的source、timer现拒、observer辣垒,互不影響;
- 如果Mode里沒有任何source印蔬、timer勋桶、observer,Runloop立刻退出;
CFRunLoopModeRef(模式)
源碼中對(duì)CFRunLoopModeRef的定義:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
....
};
從源碼中可以看到每個(gè)Mode包含若干個(gè)source0例驹、source1捐韩、timer、observer鹃锈;
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
KCFRunLoopDefaultMode:APP的默認(rèn)Mode荤胁,通常主線程在這個(gè)Mode下運(yùn)行;
UITrackingRunLoopMode:界面跟蹤Mode屎债,用于scrollView追蹤觸摸滑動(dòng)仅政,保證界面滑動(dòng)不受其他Mode影響;
UIInitializationRunLoopMode:在剛啟動(dòng)APP時(shí)進(jìn)入的第一個(gè)Mode盆驹,啟動(dòng)完成后就不再使用圆丹;
GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode,通常用不到躯喇;
KCFRunloopCommonModes:這是一個(gè)占位用的Mode辫封,不是真正的Mode;事件被標(biāo)記為KCFRunloopCommonMode后廉丽,Runloop運(yùn)行在UITrackingRunLoopMode和kCFRunLoopDefaultMode倦微,事件都能執(zhí)行;
CFRunLoopSourceRef(事件源)
Source0:觸摸事件正压、performSelector:onThread:
Source1:基于Port的線程間通信璃诀、系統(tǒng)事件捕捉,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()蔑匣。
當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后劣欢,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按鍵(鎖屏/靜音等)裁良,觸摸凿将,加速,接近傳感器等幾種 Event价脾,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進(jìn)程牧抵。隨后蘋果注冊(cè)的那個(gè) Source1 就會(huì)觸發(fā)回調(diào),并調(diào)用 _UIApplicationHandleEventQueue() 進(jìn)行應(yīng)用內(nèi)部的分發(fā)侨把。
_UIApplicationHandleEventQueue() 會(huì)把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā)犀变,其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等。通常事件比如 UIButton 點(diǎn)擊秋柄、touchesBegin/Move/End/Cancel 事件都是在這個(gè)回調(diào)中完成的获枝。
RunLoopTimer
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 */
};
RunLoopTimer是基于時(shí)間的觸發(fā)器,包含一個(gè)時(shí)間長度和回調(diào)骇笔。NSTimer注冊(cè)到Runloop后省店,Runloop會(huì)為重復(fù)的時(shí)間點(diǎn)注冊(cè)好事件嚣崭,時(shí)間間隔_interval,時(shí)間點(diǎn)到時(shí)Runloop會(huì)被喚醒執(zhí)行回調(diào)懦傍;Runloop為了節(jié)省資源雹舀,不會(huì)準(zhǔn)確按照時(shí)間點(diǎn)回調(diào)timer,_tolerance表示到了時(shí)間點(diǎn)后允許的最大誤差粗俱。如果某個(gè)時(shí)間點(diǎn)被錯(cuò)過了说榆,則這個(gè)時(shí)間點(diǎn)的回調(diào)會(huì)跳過,不會(huì)延后執(zhí)行寸认。
NSTimer的處理娱俺,會(huì)受到RunLoop的Mode影響。舉個(gè)例子:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
上面這樣創(chuàng)建的處理的time只能在NSDefaultRunLoopMode下工作废麻,當(dāng)滾動(dòng)頁面時(shí),Runloop切換到UITrackingRunLoopMode模庐,timer失效烛愧;若想在NSDefaultRunLoopMode和UITrackingRunLoopMode模式下,timer都有效掂碱,應(yīng)將timer事件添加到KCFRunloopCommonModes怜姿;如下:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode: KCFRunloopCommonModes];
RunLoopObserver
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 */
};
RunLoopObserver是用來觀察Runloop的狀態(tài)的。當(dāng)Runloop狀態(tài)發(fā)生改變疼燥,觀察者能通過回調(diào)接收到這個(gè)變化沧卢。可檢測(cè)到Runloop以下狀態(tài):
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Runloop的運(yùn)行邏輯
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
從源碼可以看到醉者,Runloop做著do...while循環(huán)但狭,CFRunLoopRunSpecific函數(shù)返回值為kCFRunLoopRunStopped或kCFRunLoopRunFinished時(shí)退出循環(huán)。
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知observer即將進(jìn)入Runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// Runloop循環(huán)處理事件撬即,__CFRunLoopRun函數(shù)只有在被打斷或沒有source/timer/observer時(shí)立磁,才會(huì)返回
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知observer,Runloop即將退出
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
return result;
}
進(jìn)入CFRunLoopRunSpecific函數(shù)剥槐,說明Runloop將要開始工作了唱歧,于是通知observer即將進(jìn)入Runloop。然后調(diào)用__CFRunLoopRun函數(shù)循環(huán)處理事件粒竖,函數(shù)返回颅崩,通知observer,Runloop即將退出蕊苗。直到Runloop被中斷或事件處理完畢沿后,整個(gè)Runloop循環(huán)結(jié)束;
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
// 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)朽砰。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)得运。
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// RunLoop 觸發(fā) Source0 (非port) 回調(diào)膝蜈。
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
// 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息熔掺。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
/*
調(diào)用 mach_msg 等待接受 mach_port 的消息饱搏。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒
? 一個(gè)基于 port 的Source 的事件。
? 一個(gè) Timer 到時(shí)間了
? RunLoop 自身的超時(shí)時(shí)間到了
? 被其他什么調(diào)用者手動(dòng)喚醒
*/
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
// 通知 Observers: RunLoop 的線程剛剛被喚醒了置逻。
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 收到消息推沸,處理消息。
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
// Timer 到時(shí)間了券坞,觸發(fā)Timer的回調(diào)鬓催。
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()))
}
//有dispatch回到main_queue的block,執(zhí)行block恨锚。
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
// Source1 (基于port) 發(fā)出事件宇驾,處理這個(gè)事件
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
}
// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
// 進(jìn)入loop時(shí)參數(shù)說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
// 被外部調(diào)用者強(qiáng)制停止了
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// source/timer/observer一個(gè)都沒有了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
__CFRunLoopRun函數(shù)是進(jìn)入Runloop后的處理邏輯猴伶。
RunLoop整個(gè)運(yùn)行過程如下:
1.通知Observer课舍,將要進(jìn)入RunLoop
2.通知Observer,將要處理timer
3.通知Observer他挎,即將觸發(fā)非基于端口的輸入源(source0事件)筝尾。
4.處理block
5.觸發(fā)的source0事件(可能會(huì)再次處理)
6.如果存在source1,調(diào)轉(zhuǎn)到第8步
7.通知Observer開始休眠
8.通知Observer結(jié)束休眠
1办桨、處理timer
2筹淫、處理GCD Async To Main Queue
3、處理source19.處理Blocks
10.根據(jù)前面執(zhí)行的結(jié)果呢撞,決定如何操作
1损姜、回到第2步
2、退出RunLoop11.通知Observer退出RunLoop
9.處理待處理事件殊霞。
如果觸發(fā)了用戶定義的計(jì)時(shí)器薛匪,則處理計(jì)時(shí)器事件并重新啟動(dòng)循環(huán)。轉(zhuǎn)到第2步脓鹃。
如果輸入源被觸發(fā)逸尖,則傳遞事件。
如果運(yùn)行循環(huán)被明確喚醒但尚未超時(shí)瘸右,請(qǐng)重新啟動(dòng)循環(huán)娇跟。轉(zhuǎn)到第2步。10.通知觀察者運(yùn)行循環(huán)已退出太颤。
參考博客:
深入理解RunLoop