上篇文章講了runtime的簡單應用,使用鉤子實現了對字典和數組的賦值的校驗,順便隨手擼了一個簡單的jsonToModel
,iOS
除了runtime
還有一個東西的叫作runloop
,各位看官老爺必定都有了解,那麼今天這篇文章初識一下runloop
。html
簡單來說runloop
就是一個循環,咱們寫的程序,通常沒有循環的話,執行完就結束了,那麼咱們手機上的APP是如何一直運行不中止的呢?APP就是用到了runloop
,保證程序一直運行不退出,在須要處理事件的時候處理事件,不處理事件的時候進行休眠,跳出循環程序就結束。用僞代碼實現一個runloop
實際上是這樣子的ios
int ret = 0; do { //睡眠中等待消息 int messgae = sleep_and_wait(); //處理消息 ret = process_message(messgae); } while (ret == 0); 複製代碼
iOS中有兩套能夠獲取runloop代碼,一個是Foundation
、一個是Core Foundation
。 Foundation
實際上是對Core Foundation
的一個封裝,c++
NSRunLoop * runloop1 = [NSRunLoop currentRunLoop]; NSRunLoop *mainloop1 = [NSRunLoop mainRunLoop]; CFRunLoopRef runloop2= CFRunLoopGetCurrent(); CFRunLoopRef mainloop2 = CFRunLoopGetMain(); NSLog(@"%p %p %p %p",runloop1,mainloop1,runloop2,mainloop2); NSLog(@"%@",runloop1); //打印 runlopp1:0x600001bc58c0 mainloop1:0x600001bc58c0 runloop2:0x6000003cc300 mainloop1:0x6000003cc300 runloop1:<CFRunLoop 0x6000003cc300 [0x10b2e9ae8]>..... 複製代碼
runloop1
和mainloop1
地址一致,說明當前的runloop
是mainrunloop
,runloop1
做爲對象輸出的結果其實也是runloop2
的地址,證實Foundation runloop
是對Core Foundation
的一個封裝。git
RunLoop
底層咱們猜想應該是結構體,咱們都瞭解到其實OC
就是封裝了c/c++
,那麼c厲害之處就是指針和結構體基本解決經常使用的全部東西。咱們窺探一下runloop
的真是模樣,經過CFRunLoopRef *runloop = CFRunLoopGetMain();
查看CFRunloop
是typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
,咱們經常使用的CFRunLoopRef
是__CFRunLoop *
類型的,那麼再在源碼(能夠下載最新的源碼)中搜索一下 struct __CFRunLoop {
在runloop.c 637行
以下所示:github
struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; /* model list 鎖 */ __CFPort _wakeUpPort; // 接受 CFRunLoopWakeUp的端口 Boolean _unused;//是否使用 volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; //線程 uint32_t _winthread;//win線程 CFMutableSetRef _commonModes; //modes CFMutableSetRef _commonModeItems; //modeItems CFRunLoopModeRef _currentMode; //當前的mode CFMutableSetRef _modes; //全部的modes struct _block_item *_blocks_head; //待執行的block列表頭部 struct _block_item *_blocks_tail; //待執行的block 尾部 CFAbsoluteTime _runTime; //runtime CFAbsoluteTime _sleepTime; //sleeptime CFTypeRef _counterpart; // }; 複製代碼
通過簡化以後:web
struct __CFRunLoop {
pthread_t _pthread; //線程
CFMutableSetRef _commonModes; //modes
CFMutableSetRef _commonModeItems; //modeItems
CFRunLoopModeRef _currentMode; //當前的mode
CFMutableSetRef _modes; //全部的modes
}
複製代碼
runloop
中包含一個線程_pthread
,一一對應的CFMutableSetRef _modes
能夠有多個mode
CFRunLoopModeRef _currentMode
當前mode
只能有一個那麼mode裏邊有什麼內容呢?咱們猜想他應該和runloop
相似,在源碼中搜索CFRuntimeBase _base
看到在runloop.c line 524
看到具體的內容:json
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 */ }; 複製代碼
通過簡化以後是:數組
struct __CFRunLoopMode {
CFStringRef _name;//當前mode的名字
CFMutableSetRef _sources0;//souces0
CFMutableSetRef _sources1;//sources1
CFMutableArrayRef _observers;//observers
CFMutableArrayRef _timers;//timers
}
複製代碼
一個mode
能夠有多個timer
、souces0
、souces1
、observers
、timers
那麼使用圖更直觀的來表示:bash
runloop
包含多個
mode
,可是同時只能運行一個
mode
,這點和你們開車的駕駛模式相似,運動模式和環保模式同時只能開一個模式,不能又運動又環保,明顯相悖。多個
mode
被隔離開有點是處理事情更專注,不會由於多個同時處理事情形成卡頓或者資源競爭致使的一系列問題。
測試下點擊事件處理源markdown
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__);//此處斷點 } (LLDB) bt //輸出當前調用棧 * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x000000010c5bb66d CFRunloop`::-[ViewController touchesBegan:withEvent:](self=0x00007fc69ec087e0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000012a01b0) at ViewController.mm:22:2 frame #1: 0x0000000110685a09 UIKitCore`forwardTouchMethod + 353 frame #2: 0x0000000110685897 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49 frame #3: 0x0000000110694c48 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 1869 frame #4: 0x00000001106965d2 UIKitCore`-[UIWindow sendEvent:] + 4079 frame #5: 0x0000000110674d16 UIKitCore`-[UIApplication sendEvent:] + 356 frame #6: 0x0000000110745293 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 3232 frame #7: 0x0000000110747bb9 UIKitCore`__handleEventQueueInternal + 5911 frame #8: 0x000000010d8eabe1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 frame #9: 0x000000010d8ea463 CoreFoundation`__CFRunLoopDoSources0 + 243 frame #10: 0x000000010d8e4b1f CoreFoundation`__CFRunLoopRun + 1231 frame #11: 0x000000010d8e4302 CoreFoundation`CFRunLoopRunSpecific + 626 frame #12: 0x0000000115ddc2fe GraphicsServices`GSEventRunModal + 65 frame #13: 0x000000011065aba2 UIKitCore`UIApplicationMain + 140 frame #14: 0x000000010c5bb760 CFRunloop`main(argc=1, argv=0x00007ffee3643f68) at main.m:14:13 frame #15: 0x000000010f1cb541 libdyld.dylib`start + 1 frame #16: 0x000000010f1cb541 libdyld.dylib`start + 1 複製代碼
#1
看到如今是在隊列queue = 'com.apple.main-thread'中,#10
Runloop
啓動,#9
進入到__CFRunLoopDoSources0
,最終__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
調用了__handleEventQueueInternal
->[UIApplication sendEvent:]
->[UIWindow sendEvent:]
->[UIWindow _sendTouchesForEvent:]
->[UIResponder touchesBegan:withEvent:]
->-[ViewController touchesBegan:withEvent:](self=0x00007fc69ec087e0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000012a01b0) at ViewController.mm:22:2
,能夠看到另一個知識點,手勢的傳遞是從上往下的,順序是UIApplication -> UIWindow -> UIResponder -> ViewController
。
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); static int count = 5; dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ NSLog(@"-------:%d \n",count++); }); dispatch_resume(timer); //log (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000101f26457 CFRunloop`::__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x0000000101f28100) at ViewController.mm:72:33 frame #1: 0x0000000104ac2db5 libdispatch.dylib`_dispatch_client_callout + 8 frame #2: 0x0000000104ac5c95 libdispatch.dylib`_dispatch_continuation_pop + 552 frame #3: 0x0000000104ad7e93 libdispatch.dylib`_dispatch_source_invoke + 2249 frame #4: 0x0000000104acfead libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1073 frame #5: 0x00000001032568a9 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 frame #6: 0x0000000103250f56 CoreFoundation`__CFRunLoopRun + 2310 frame #7: 0x0000000103250302 CoreFoundation`CFRunLoopRunSpecific + 626 複製代碼
最終進入函數__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
調用了libdispatch的_dispatch_main_queue_callback_4CF
函數,具體實現有興趣的大佬能夠看下源碼的實現。
Mode
類型都多個,系統暴露在外的就兩個,
CF_EXPORT const CFRunLoopMode kCFRunLoopDefaultMode;
CF_EXPORT const CFRunLoopMode kCFRunLoopCommonModes;
複製代碼
那麼這兩個Mode都是在什麼狀況下運行的呢?
kCFRunLoopDefaultMode(NSDefaultRunLoopMode)
:App
的默認Mode
,一般主線程是在這個Mode
下運行UITrackingRunLoopMode
:界面跟蹤Mode
,用於ScrollView
追蹤觸摸滑動,保證界面滑動時不受其餘Mode
影響進入到某個Mode
,處理事情也應該有前後順序和休息的時間,那麼如今須要一個狀態來表示此時此刻的status
,系統已經準備了CFRunLoopActivity
來表示當前的狀態
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進入loop
kCFRunLoopBeforeTimers = (1UL << 1),//即將處理timers
kCFRunLoopBeforeSources = (1UL << 2), //即將處理sourcs
kCFRunLoopBeforeWaiting = (1UL << 5),//即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//即將從休眠中喚醒
kCFRunLoopExit = (1UL << 7),//即將退出
kCFRunLoopAllActivities = 0x0FFFFFFFU//全部狀態
};
複製代碼
1UL
表示無符號長整形數字1
,再次看到這個(1UL << 1)
我麼猜想用到了位域或者聯合體,達到省空間的目的。kCFRunLoopAllActivities = 0x0FFFFFFFU
轉換成二進制就是28個1
,再進行mask
的時候,全部的值都能取出來。
如今咱們瞭解到:
CFRunloopRef
表明RunLoop
的運行模式Runloop
包含若干個Mode
,每一個Mode
包含若干個Source0/Source1/Timer/Obser
Runloop
啓動只能選擇一個Mode
做爲currentMode
Mode
,只能退出當前Loop
,再從新選擇一個Mode
進入Source0/Source1/Timer/Observer
能分隔開來,互不影響Mode
沒有任何Source0/Source1/Timer/Observer
,Runloop
立馬退出。CFRunLoopObserverRef obs= CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry:{ CFRunLoopMode m = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"即將進入 mode:%@",m); CFRelease(m); break; } case kCFRunLoopExit: { CFRunLoopMode m = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"即將退出 mode:%@",m); CFRelease(m); break; } default: break; } }); CFRunLoopAddObserver(CFRunLoopGetMain(), obs, kCFRunLoopCommonModes); CFRelease(obs); //當滑動tb的時候log 即將退出 mode:kCFRunLoopDefaultMode 即將進入 mode:UITrackingRunLoopMode 即將退出 mode:UITrackingRunLoopMode 即將進入 mode:kCFRunLoopDefaultMode 複製代碼
當runloop
切換mode
的時候,會退出當前kCFRunLoopDefaultMode
,加入到其餘的UITrackingRunLoopMode
,當前UITrackingRunLoopMode
完成以後再退出以後再加入到kCFRunLoopDefaultMode
。
咱們再探究下runloop
的循環的狀態究竟是怎樣來變動的。
// //獲取loop CFRunLoopRef ref = CFRunLoopGetMain(); //獲取obs CFRunLoopObserverRef obs = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, callback, NULL); //添加監聽 CFRunLoopAddObserver(ref, obs, CFRunLoopCopyCurrentMode(ref)); CFRelease(obs); int count = 0;//定義全局變量來計算一個mode中狀態切換的統計數據 void callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){ printf("- "); count ++; printf("%d",count); switch (activity) { case kCFRunLoopEntry: printf("即將進入 \n"); count = 0; break; case kCFRunLoopExit: printf("即將退出 \n"); break; case kCFRunLoopAfterWaiting: printf("即將從休眠中喚醒 \n"); break; case kCFRunLoopBeforeTimers: printf("即將進入處理 timers \n"); break; case kCFRunLoopBeforeSources: printf("即將進入 sources \n"); break; case kCFRunLoopBeforeWaiting: printf("即將進入 休眠 \n"); count = 0; break; default: break; } } //點擊的時候 會出發loop來處理觸摸事件 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); } //log - 1即將從休眠中喚醒 - 2即將進入處理 timers - 3即將進入 sources -[ViewController touchesBegan:withEvent:] - 4即將進入處理 timers - 5即將進入 sources - 6即將進入處理 timers - 7即將進入 sources - 8即將進入處理 timers - 9即將進入 sources - 10即將進入 休眠 - 1即將從休眠中喚醒 - 2即將進入處理 timers - 3即將進入 sources - 4即將進入處理 timers - 5即將進入 sources - 6即將進入 休眠 - 1即將從休眠中喚醒 - 2即將進入處理 timers - 3即將進入 sources - 4即將進入 休眠 複製代碼
runloop
喚醒以後不是立馬處理事件的,而是看看timer
有沒有事情,而後是sources
,發現有觸摸事件就處理了,而後又循環查看timer
和sources
通常循環2次進入休眠狀態,處理source
以後是循環三次。
RunLoop
是在主動獲取的時候纔會生成一個,主線程是系統本身調用生成的,子線程開發者調用,咱們看下CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); } 複製代碼
看到到這裏相信你們已經對runloop
有了基本的認識,那麼咱們再探究一下底層runloop
是怎麼運轉的。
首先看官方給的圖:
那我又整理了一個表格來更直觀的瞭解狀態運轉步驟 | 任務 |
---|---|
1 | 通知Observers:進入Loop |
2 | 通知Observers:即將處理Timers |
3 | 通知Observers:即將處理Sources |
4 | 處理blocks |
5 | 處理Source0(可能再處理Blocks) |
6 | 若是存在Source1,跳轉第8步 |
7 | 通知Observers:開始休眠 |
8 | 通知Observers:結束休眠1.處理Timer2.處理GCD Asyn To Main Queue 3.處理Source1 |
9 | 處理Blocks |
10 | 根據前面的執行結果,決定如何操做1.返回第2步,2退出loop |
11 | 通知Observers:退出Loop |
查看runloop源碼中runloop.c
2333行
//入口函數 static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); return kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; return kCFRunLoopRunStopped; } 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 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 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 = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground(); 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; } Boolean didDispatchPortLastTime = true; int32_t retVal = 0; do { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED; voucher_t voucherCopy = NULL; #endif 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; #endif __CFPortSet waitSet = rlm->_portSet; __CFRunLoopUnsetIgnoreWakeUps(rl); //通知即將處理Timers if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); //通知即將處理Sources if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); //處理Blocks __CFRunLoopDoBlocks(rl, rlm); //處理Source0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { //處理Block __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI msg = (mach_msg_header_t *)msg_buffer; //y判斷是否有Source1 if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { //有則去 handle_msg goto handle_msg; } #endif } didDispatchPortLastTime = false; //即將進入休眠 if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); //開始休眠 __CFRunLoopSetSleeping(rl); __CFPortSetInsert(dispatchPort, waitSet); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent(); #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI #if USE_DISPATCH_SOURCE_FOR_TIMERS do { if (kCFUseCollectableAllocator) { 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, &voucherState, &voucherCopy); if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { (_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); #else if (kCFUseCollectableAllocator) { 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, &voucherState, &voucherCopy); #endif __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart)); __CFPortSetRemove(dispatchPort, waitSet); __CFRunLoopSetIgnoreWakeUps(rl); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) //結束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //標籤 handle_msg handle_msg:; __CFRunLoopSetIgnoreWakeUps(rl); if (MACH_PORT_NULL == livePort) { CFRUNLOOP_WAKEUP_FOR_NOTHING(); // handle nothing } else if (livePort == rl->_wakeUpPort) { CFRUNLOOP_WAKEUP_FOR_WAKEUP(); } #if USE_DISPATCH_SOURCE_FOR_TIMERS else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { //被timer喚醒 CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { __CFArmNextTimerInMode(rlm, rl); } } #endif #if USE_MK_TIMER_TOO else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { __CFArmNextTimerInMode(rlm, rl); } } #endif //被GCD換醒 else if (livePort == dispatchPort) { CFRUNLOOP_WAKEUP_FOR_DISPATCH(); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL); //處理GCD __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(); voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release); 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; 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); } #endif } _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release); } //處理bBlock __CFRunLoopDoBlocks(rl, rlm); //設置返回值 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; } #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_revert(voucherState); os_release(voucherCopy); #endif } while (0 == retVal); if (timeout_timer) { dispatch_source_cancel(timeout_timer); dispatch_release(timeout_timer); } else { free(timeout_context); } return retVal; } 複製代碼
通過及進一步精簡
//入口函數 static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); return kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; return kCFRunLoopRunStopped; } Boolean didDispatchPortLastTime = true; int32_t retVal = 0; do { __CFRunLoopUnsetIgnoreWakeUps(rl); //通知即將處理Timers if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); //通知即將處理Sources if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); //處理Blocks __CFRunLoopDoBlocks(rl, rlm); //處理Source0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { //處理Block __CFRunLoopDoBlocks(rl, rlm); } msg = (mach_msg_header_t *)msg_buffer; //y判斷是否有Source1 if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { //有則去 handle_msg goto handle_msg; } //即將進入休眠 if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); //開始休眠 __CFRunLoopSetSleeping(rl); do { //等待消息來喚醒當前線程 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); } while (1); #else if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) //結束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //標籤 handle_msg handle_msg:; //被timer喚醒 CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { __CFArmNextTimerInMode(rlm, rl); } #if USE_MK_TIMER_TOO else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { __CFArmNextTimerInMode(rlm, rl); } } #endif //被GCD換醒 else if (livePort == dispatchPort) { //處理GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } else { CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); //處理Source1 sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; // Restore the previous voucher _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release); } //處理bBlock __CFRunLoopDoBlocks(rl, rlm); //設置返回值 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); return retVal; } 複製代碼
精簡到這裏基本都能看懂了,還寫了不少註釋,基本和上面整理的表格一致。 這裏的線程休眠__CFRunLoopServiceMachPort
是調用內核函數mach_msg()進行休眠,和咱們平時while(1)
大不一樣,while(1)
叫死循環,其實系統每時每刻都在判斷是否符合條件,耗費很高的CPU,內核則不一樣,Mach內核提供面向消息,基於基礎的進程間通訊。
一個程序運行完畢結束了就死掉了,timer
和變量也同樣,運行完畢就結束了,那麼咱們怎麼能夠保證timer
一直活躍和線程不結束呢?
timer
能夠添加到self
的屬性保證一直活着,只要self
不死,timer
就不死。timer
默認是添加到NSDefaultRunLoopMode
模式中,由於RunLoop
同時運行只能有一個模式,那麼在滑動scroller
的時候怎Timer
會卡頓中止直到再次切換回來,那麼如何保證同時兩個模式均可以運行呢? Foundation
提供了一個API(void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode
添加上,mode
值爲NSRunLoopCommonModes
能夠保證同時兼顧2種模式。
測試代碼:
static int i = 0; NSTimer *timer=[NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"%d",++i); }]; //NSRunLoopCommonModes 並非一個真正的模式,它這仍是一個標記 //timer在設置爲common模式下能運行 //NSRunLoopCommonModes 能在 _commentModes中數組中的模式均可以運行 //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];//默認的模式 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //log 2019-07-23 15:14:31 CFRunloop[62358:34093079] 1 2019-07-23 15:14:32 CFRunloop[62358:34093079] 2 2019-07-23 15:14:33 CFRunloop[62358:34093079] 3 2019-07-23 15:14:34 CFRunloop[62358:34093079] 4 2019-07-23 15:14:35 CFRunloop[62358:34093079] 5 2019-07-23 15:14:36 CFRunloop[62358:34093079] 6 2019-07-23 15:14:37 CFRunloop[62358:34093079] 7 2019-07-23 15:14:38 CFRunloop[62358:34093079] 8 複製代碼
當滑動的時候timer
的時候,timer
仍是如此絲滑,沒有一點停頓。 沒有卡頓以後咱們VC -> dealloc
中timer
仍是在執行,那麼須要在dealloc
中去下和刪除觀察者
-(void)dealloc{ NSLog(@"%s",__func__); CFRunLoopRemoveObserver(CFRunLoopGetMain(), obs, m); dispatch_source_cancel(timer); } 複製代碼
退出vc
以後dealloc
照常執行,日誌只有-[ViewController dealloc]
,並且數字沒有繼續輸出,說明刪除觀察者和取消source
都成功了。
那麼NSRunLoopCommonModes
是另一種模式嗎?
經過源碼查看得知,在runloop.c line:1632 line:2608
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)); } 複製代碼
還有不少地方都可以看出,當是currentMode
須要和_mode
相等纔去執行,當是kCFRunLoopCommonModes
的時候,只須要包含curMode
便可執行。可見kCFRunLoopCommonModes
實際上是一個集合,不是某個特定的mode
。
線程爲何須要保活?性能其實很大的瓶頸是在於空間的申請和釋放,當咱們執行一個任務的時候建立了一個線程,任務結束就釋放掉該線程,若是任務頻率比較高,那麼一個一直活躍的線程來執行咱們的任務就省去申請和釋放空間的時間和性能。上邊已經講過了 runloop
須要有任務才能不退出,總不可能直接讓他執行while(1)
吧,這種方法明顯不對的,由源碼得知,當有監測端口的時候,也不會退出,也不會影響應能。因此在線程初始化的時候使用
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes]; 複製代碼
來保活。 在主線程使用是沒有意義的,系統已經在APP啓動的時候進行了調用,則已經加入到全局的字典中了。
驗證線程保活
@property (nonatomic,strong) FYThread *thread; - (void)viewDidLoad { [super viewDidLoad]; self.thread=[[FYThread alloc]initWithTarget:self selector:@selector(test) object:nil]; _thread.name = @"test thread"; [_thread start]; } - (void)test { //添加端口 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; NSLog(@"%@",[NSThread currentThread]); NSLog(@"--start--"); [[NSRunLoop currentRunLoop] run]; NSLog(@"--end--"); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); [self performSelector:@selector(alive) onThread:self.thread withObject:nil waitUntilDone:NO]; NSLog(@"執行完畢了子線程");//不執行 由於子線程保活了 不會執行完畢 } //測試子線程是否還活着 - (void)alive{ NSLog(@"我還活着呢->%@",[NSThread currentThread]); } //log //註釋掉添加端口代碼 <FYThread: 0x6000013a9540>{number = 3, name = test thread} --start-- --end-- -[ViewController touchesBegan:withEvent:] 執行完畢了子線程 //註釋放開的時候點擊觸發log <FYThread: 0x6000013a9540>{number = 3, name = test thread} --start-- -[ViewController touchesBegan:withEvent:] 執行完畢了子線程 我還活着呢-><FYThread: 0x6000017e5c80>{number = 3, name = test thread} 複製代碼
[[NSRunLoop currentRunLoop] addPort:[NSPort port]forMode:NSDefaultRunLoopMode]
添加端口註釋掉,直接執行了--end--
,線程雖然strong
強引用,可是runloop
已經退出了,因此函數alive
沒有執行,不註釋的話,alive
還會執行,end
一直不會執行,由於進入了runloop
,並且沒有退出,代碼就不會向下執行。
那咱們測試下該線程聲明週期多長?
- (void)viewDidLoad { [super viewDidLoad]; self.thread=[[FYThread alloc]initWithTarget:self selector:@selector(test) object:nil]; _thread.name = @"test thread"; [_thread start]; } - (void)test { [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; //獲取obs NSLog(@"%@",[NSThread currentThread]); NSLog(@"--start--"); /* If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers. */ [[NSRunLoop currentRunLoop] run]; NSLog(@"--end--"); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); [self performSelector:@selector(alive) onThread:self.thread withObject:nil waitUntilDone:NO]; NSLog(@"執行完畢了子線程");//不執行 由於子線程保活了 不會執行完畢 } //返回上頁 - (IBAction)popVC:(id)sender { [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:NO]; } //測試子線程是否還活着 - (void)alive{ NSLog(@"我還活着呢->%@",[NSThread currentThread]); } //中止子線程線程 - (void)stop{ CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s",__func__); } - (void)dealloc{ NSLog(@"%s",__func__); } //log <FYThread: 0x600003394780>{number = 3, name = test thread} --start-- -[ViewController stop] -[ViewController stop] 複製代碼
擁有該線程的是VC
,點擊pop
的時候,可是VC
和thread
沒釋放掉,好像thread
和VC
創建的循環引用,當self.thread=[[FYThread alloc]initWithTarget:self selector:@selector(test) object:nil];
註釋了,則VC
能夠進行正常釋放。
經過測試瞭解到 這個線程達到了永生,就是你殺不死他,簡直了死待。查找了很多資料才發現官方文檔纔是最穩的。有對這句[[NSRunLoop currentRunLoop] run]
的解釋
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
就是系統寫了以一個死循環可是沒有阻止他的參數,至關於一直在循環調用 runMode:beforeDate:
,那麼該怎麼辦呢? 官方文檔給出瞭解決方案
BOOL shouldKeepRunning = YES; // global NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); 複製代碼
將代碼改爲下面的成功將死待殺死了。
- (void)test { [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; //獲取obs NSLog(@"%@",[NSThread currentThread]); NSLog(@"--start--"); self.shouldKeepRunning = YES;//默認運行 NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (_shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); NSLog(@"--end--"); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"%s",__func__); [self performSelector:@selector(alive) onThread:self.thread withObject:nil waitUntilDone:NO]; NSLog(@"執行完畢了子線程");//不執行 由於子線程保活了 不會執行完畢 } //返回上頁 - (IBAction)popVC:(id)sender { self.shouldKeepRunning = NO; [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:NO]; } //測試子線程是否還活着 - (void)alive{ NSLog(@"我還活着呢->%@",[NSThread currentThread]); } //中止子線程線程 - (void)stop{ CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s",__func__); [self performSelectorOnMainThread:@selector(pop) withObject:nil waitUntilDone:NO]; } - (void)pop{ [self.navigationController popViewControllerAnimated:YES]; } - (void)dealloc{ NSLog(@"%s",__func__); } //log <FYThread: 0x600002699fc0>{number = 3, name = test thread} --start-- -[ViewController stop] --end-- -[ViewController dealloc] -[FYThread dealloc] 複製代碼
點擊popVC:
首先將self.shouldKeepRunning = NO
,而後子線程執行CFRunLoopStop(CFRunLoopGetCurrent())
,而後在主線程執行pop
函數,最終返回上級頁面並且成功殺死VC
和死待。 固然這個死待其實也是有用處的,當使用單例模式做爲下載器的時候使用死待也沒問題。這樣子處理比較複雜,咱們能夠放在VC
的dealloc
看看是否能成功。 關鍵函數稍微更改:
//中止子線程線程 - (void)stop{ if (self.thread == nil) { return; } NSLog(@"%s",__func__); [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO]; } - (void)stopThread{ self.shouldKeepRunning = NO; CFRunLoopStop(CFRunLoopGetCurrent()); } - (void)dealloc{ [self stop]; NSLog(@"%s",__func__); } 複製代碼
當點擊返回按鈕VC
和線程都沒死,原來他們造成了強引用沒法釋放,就是VC
始終沒法執行dealloc
。將函數改爲block
實現
__weak typeof(self) __weakSelf = self; self.thread = [[FYThread alloc]initWithBlock:^{ [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; NSLog(@"%@",[NSThread currentThread]); NSLog(@"--start--"); __weakSelf.shouldKeepRunning = YES;//默認運行 NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (__weakSelf && __weakSelf.shouldKeepRunning ){ [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }; NSLog(@"--end--"); }]; 複製代碼
測試下崩潰了,崩潰到了:
while (__weakSelf.shouldKeepRunning ){ [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];//崩潰的地方 }; 複製代碼
怎麼想感受不對勁啊,怎麼會不行呢?VC
銷燬的時候調用子線程stop
,最後打斷點發現到了崩潰的地方self
已經不存在了,說明是異步執行的,往前查找使用異步的函數最後出如今了[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
,表示不用等待stopThread
函數執行時間,直接向前繼續執行,因此VC
釋放掉了,while (__weakSelf.shouldKeepRunning )
是true
,還真進去了,訪問了exe_bad_access
,因此改爲while (__weakSelf&&__weakSelf.shouldKeepRunning )
再跑一下
//log --start-- -[ViewController stop] -[ViewController dealloc] --end-- -[FYThread dealloc] 複製代碼
如牛奶般絲滑,解決了釋放問題,也解決了複雜操做。本文章全部代碼均在底部連接能夠下載。 使用這個思路本身封裝了一個簡單的功能,你們能夠本身封裝一下而後對比一下個人思路,說不定有驚喜!
最怕一輩子碌碌無爲,還安慰本身平凡難得。
廣告時間