一糜值、RunLoop的入口
通過(guò)再touchesBegan方法中添加斷點(diǎn),使用bt指令,可以顯示出方法調(diào)用棧
frame #1: 0x00007fff480bf863 UIKitCore`forwardTouchMethod + 340
frame #2: 0x00007fff480bf6fe UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x00007fff480ce8de UIKitCore`-[UIWindow _sendTouchesForEvent:] + 1867
frame #4: 0x00007fff480d04c6 UIKitCore`-[UIWindow sendEvent:] + 4596
frame #5: 0x00007fff480ab53b UIKitCore`-[UIApplication sendEvent:] + 356
frame #6: 0x00007fff4812c71a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 6847
frame #7: 0x00007fff4812f1e0 UIKitCore`__handleEventQueueInternal + 5980
frame #8: 0x00007fff23bd4471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x00007fff23bd439c CoreFoundation`__CFRunLoopDoSource0 + 76
frame #10: 0x00007fff23bd3b74 CoreFoundation`__CFRunLoopDoSources0 + 180
frame #11: 0x00007fff23bce87f CoreFoundation`__CFRunLoopRun + 1263
frame #12: 0x00007fff23bce066 CoreFoundation`CFRunLoopRunSpecific + 438
frame #13: 0x00007fff384c0bb0 GraphicsServices`GSEventRunModal + 65
frame #14: 0x00007fff48092d4d UIKitCore`UIApplicationMain + 1621
frame #15: 0x0000000103c46fb4 runloop`main(argc=1, argv=0x00007ffeebfb8d00) at main.m:18:12
frame #16: 0x00007fff5227ec25 libdyld.dylib`start + 1
從下到上的程序調(diào)用方法的過(guò)程橄抹,再UIApplicationMain之后調(diào)用了CFRunloop,其中CFRunLoopRunSpecific 方法是runloop的具體內(nèi)容蜜笤。
C語(yǔ)言的API CFRunLoopRef ,C語(yǔ)言的runloop 是開(kāi)源的濒蒋,下載地址 https://opensource.apple.com/tarballs/CF/
在源碼中,我們尋找CFRunLoopRunSpecific方法把兔,看看具體都執(zhí)行了哪些內(nèi)容
方法內(nèi)部有這樣一段代碼:
// 經(jīng)過(guò)精簡(jiǎn)的 CFRunLoopRunSpecific 函數(shù)代碼沪伙,其內(nèi)部會(huì)調(diào)用__CFRunLoopRun函數(shù)
/*
* 指定mode運(yùn)行runloop
* @param rl 當(dāng)前運(yùn)行的runloop
* @param modeName 需要運(yùn)行的mode的name
* @param seconds runloop的超時(shí)時(shí)間
* @param returnAfterSourceHandled 是否處理完事件就返回
*/
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
CHECK_FOR_FORK();
// RunLoop正在釋放,完成返回
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 根據(jù)modeName 取出當(dāng)前的運(yùn)行Mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false
// 如果沒(méi)找到 || mode中沒(méi)有注冊(cè)任何事件县好,則就此停止围橡,不進(jìn)入循環(huán)
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
//取上一次運(yùn)行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//如果本次mode和上次的mode一致
rl->_currentMode = currentMode;
//初始化一個(gè)result為kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry )
// 1. 通知 Observers: 進(jìn)入RunLoop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 2.RunLoop的運(yùn)行循環(huán)的核心代碼
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit )
// 12. 通知 Observers: 退出RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
上面代碼包含了以下內(nèi)容
1.如果當(dāng)前mode中沒(méi)有注冊(cè)任何的事件缕贡,不會(huì)進(jìn)入runloop
2.進(jìn)入runloop 發(fā)送entry通知 kCFRunLoopEntry
3.執(zhí)行runloop核心內(nèi)容在__CFRunLoopRun方法
4.runloop結(jié)束翁授,發(fā)送退出runloop通知 kCFRunLoopExit
__CFRunLoopRun 方法中具有runloop的整個(gè)執(zhí)行過(guò)程,其中主要代碼
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
/// 方法內(nèi)部就是一個(gè) do...while 循環(huán)
int32_t retVal = 0;
do {
// 1.發(fā)送observer 通知 kCFRunLoopBeforeTimers 即將處理timer
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 2.發(fā)送observer通知kCFRunLoopBeforeSources 即將處理source
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 3.開(kāi)始處理blocks
__CFRunLoopDoBlocks(rl, rlm);
// 開(kāi)始處理source
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 有可能還會(huì)繼續(xù)處理blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 判斷是否有source1晾咪,如果有source1 跳轉(zhuǎn)到handle_msg步驟執(zhí)行
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// 如果有source1 跳轉(zhuǎn)到handle_msg
goto handle_msg;
}
// 如果沒(méi)有source1 發(fā)送通知即將進(jìn)入休眠狀態(tài)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
do { // 等待別的消息喚醒線(xiàn)程 收擦, 一旦喚醒,會(huì)繼續(xù)向下走禀酱,如果沒(méi)有炬守,則會(huì)阻塞在這
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// 線(xiàn)程被喚醒,結(jié)束休眠狀態(tài)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
/// 被喚醒之后剂跟,同樣會(huì)進(jìn)入handle_msg 中减途,判斷是如何被喚醒的酣藻,處理事件
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (被timer喚醒) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}else if (被GCD喚醒) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { 被source1喚醒
CFRUNLOOP_WAKEUP_FOR_SOURCE();
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
// 處理block
__CFRunLoopDoBlocks(rl, rlm);
//繼續(xù)循環(huán)返回第一步,依次向下執(zhí)行
} while (0 == retVal);
return retVal;
}
總結(jié)runloop執(zhí)行流程
二鳍置、RunLoop的應(yīng)用
RunLoop的五種模式
(1) kCFRunLoopDefaultMode:App的默認(rèn)Mode辽剧,通常主線(xiàn)程是在這個(gè)Mode下運(yùn)行
(2)UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動(dòng)税产,保證界面滑動(dòng)時(shí)不受其他 Mode 影響
(3) UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)進(jìn)入的第一個(gè) Mode怕轿,啟動(dòng)完成后就不再使用,會(huì)切換到kCFRunLoopDefaultMode
(4)GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode辟拷,通常用不到
(5)kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode撞羽,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode
1.NSTimer
NSTimer的運(yùn)行是基于RunLoop的衫冻,當(dāng)被添加到RunLoop中之后诀紊,RunLoop會(huì)根據(jù)它的時(shí)間間隔注冊(cè)相應(yīng)的時(shí)間點(diǎn),到時(shí)間點(diǎn)之后隅俘,喚醒RunLoop觸發(fā)timer事件邻奠,所以使用NSTimer 的時(shí)候,要將timer添加到runloop中并且制定mode为居。否則碌宴,將不會(huì)執(zhí)行
方法1.創(chuàng)建timer,手動(dòng)添加到RunLoop中蒙畴,如果不添加贰镣,將不會(huì)執(zhí)行timer
__block int i = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d",i);
i++;
}];
/// 將timer添加到當(dāng)前runloop的default模式下,如果不添加這段代碼忍抽,timer將不會(huì)被執(zhí)行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
方法2.自動(dòng)添加到當(dāng)前的Runloop 的default模式下
__block int i = 0;
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d",i);
i++;
}];
NSTimer 在UITableView/UIScrollView/UICollectionView中滑動(dòng)的時(shí)候八孝,會(huì)導(dǎo)致失效。
原因是鸠项,在滾動(dòng)的時(shí)候干跛,主線(xiàn)程中的RunLoop切換到了UITrackingRunLoopMode模式下,如果timer不在這個(gè)模式里面祟绊,將會(huì)導(dǎo)致timer不會(huì)被執(zhí)行楼入,因此在這種情況下,需要同時(shí)將timer添加到NSDefaultRunLoopMode和UITrackingRunLoopMode模式下牧抽,或者直接使用 NSRunLoopCommonModes
注意:
使用NSTimer的時(shí)候可能會(huì)造成時(shí)間不準(zhǔn)確和循環(huán)引用無(wú)法退出的問(wèn)題嘉熊。
1.1 NSTimer的循環(huán)引用問(wèn)題
如果使用方法
[NSTimer timerWithTimeInterval:(NSTimeInterval) target:(nonnull id) selector:(nonnull SEL) userInfo:(nullable id) repeats:(BOOL)]
創(chuàng)建NSTimer,并且控制器擁一個(gè)屬性 timer 指向這個(gè)NSTimer扬舒,那么就會(huì)產(chǎn)出循環(huán)引用
原因是阐肤,控制器強(qiáng)引用NSTimer,NSTimer中的target屬性又強(qiáng)引用控制器,最終導(dǎo)致無(wú)法釋放
單純的使用弱引用是無(wú)法解決這個(gè)問(wèn)題孕惜,__weak 弱指針是用于Block的解決方案愧薛。此時(shí)應(yīng)該使用一個(gè)代理,NSProxy衫画。原理是毫炉,控制器強(qiáng)引用NSTimer,NSTimer強(qiáng)引用NSProxy削罩,NSProxy弱引用控制器瞄勾。注意,NSProxy沒(méi)有方法實(shí)現(xiàn)弥激,它主要是應(yīng)用消息轉(zhuǎn)發(fā)機(jī)制进陡,將消息轉(zhuǎn)發(fā)給控制器,還是由控制器實(shí)現(xiàn)具體操作內(nèi)容秆撮。
1.2 NSTimer的不準(zhǔn)確性
使用NSTimer定時(shí)器的時(shí)候四濒,有可能會(huì)造成時(shí)間的不準(zhǔn)確性换况。
因?yàn)镹STimer 是通過(guò)RunLoop實(shí)現(xiàn)的职辨。
而RunLoop處理定時(shí)器的方法是:
RunLoop會(huì)記錄執(zhí)行一圈需要多少時(shí)間。如果定時(shí)器是1秒鐘執(zhí)行一次戈二,而RunLoop跑一圈是0.2秒舒裤,那么RunLoop跑5圈的時(shí)候,才會(huì)去執(zhí)行一次timer事件觉吭。然而腾供,RunLoop跑一圈的時(shí)間,有時(shí)會(huì)比較長(zhǎng)鲜滩,可能會(huì)跑5圈時(shí)候伴鳖,超過(guò)了1秒。所以徙硅,執(zhí)行timer事件的時(shí)間就會(huì)比1秒長(zhǎng)榜聂,導(dǎo)致NSTimer具有不準(zhǔn)確性
可以使用GCD做定時(shí)器,因?yàn)镚CD的定時(shí)器是直接操作內(nèi)核的嗓蘑,不需要RunLoop须肆。
實(shí)現(xiàn)代碼:
// 執(zhí)行的隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
// 創(chuàng)建定時(shí)器
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設(shè)置時(shí)間
// start 開(kāi)始時(shí)間
NSTimeInterval start = 2.0;//兩秒后開(kāi)始
// interval 執(zhí)行間隔時(shí)間
NSTimeInterval intervla = 1.0;// 執(zhí)行的時(shí)間間隔
// leeway 誤差
dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), intervla * NSEC_PER_SEC, 0);
// 設(shè)置回調(diào)
dispatch_source_set_event_handler(_source, ^{
NSLog(@"123");
});
// 啟動(dòng)定時(shí)器
dispatch_resume(_source);