Runloop 是和線程緊密相關的一個基礎組件,是不少線程有關功能的幕後功臣。 本文將從如下幾個方面來總結runloop:html
runloop
的run
方法源碼以下所示,是一個do..while循環服務器
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
其實是一個對象,這個對象提供了一個入口函數。在iOS系統裏,下面的這些都有使用runloop,經過斷點查看堆棧能夠看到調用的方法名:網絡
在timer
的block
裏添加斷點,而後左邊箭頭指示的按鈕不選中(默認是選中的),能夠看到runloop的調用信息__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
源碼以下:多線程
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) { if (func) { func(timer, info); } getpid(); // thwart tail-call optimization }
關於上面總結的其餘幾種調用的runloop方法名,均可以用上面的這種調試方式查看一下。app
[[NSRunLoop currentRunLoop] run]
)圖中展示了 Runloop 在線程中的做用:從 input source
和 timer source
接受事件,而後在線程中處理事件。框架
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; }
源碼裏調用了_CFRunLoopGet0()
,這裏是傳一個主線程pthread_main_thread_np()
進去,以下定義了它是主線程socket
#if DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_IPHONESIMULATOR CF_EXPORT pthread_t _CF_pthread_main_thread_np(void); #define pthread_main_thread_np() _CF_pthread_main_thread_np()
還有一個獲取當前線程runloop的方法:一樣是調用了_CFRunLoopGet0
,只不過傳進去的是當前線程pthread_self()
async
CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); }
接下來看獲取線程runloop的函數_CFRunLoopGet0
(包括主線程和子線程)的源碼函數
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) { //根據線程獲取runloop t = pthread_main_thread_np(); } __CFSpinLock(&loopsLock); //若是存儲RunLoop的字典不存在 if (!__CFRunLoops) { __CFSpinUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); //建立主線程的RunLoop 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); } //字典裏找runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFSpinUnlock(&loopsLock); if (!loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } __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); } }
CFMutableDictionaryRef
可變字典CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
把主線程和它的runloop用key-value形式保存在這個CFMutableDictionaryRef
字典容器裏CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
,能夠用線程
把保存在字典裏的runloop
取出來runloop
,就根據當前的子線程建立一個新的runloop
對象並保存到字典裏if (pthread_equal(t, pthread_self())) {...}
判斷當前的線程是否是傳遞進來的線程,若是是則建立一個回調,若是線程銷燬,就銷燬當前的runloop在AppDelegate
打斷點,能夠看到主線程是有調用__CFRunloopRun
方法的,因此證實了上面的結論三: 主線程是默認開啓runloop
的 [圖片上傳失敗...(image-ad314f-1571322546197)]) 測試runloop
代碼以下
- (vod)viewDidLoad { super viewDidLoad]; DLThread *thread = [[DLThread alloc]initWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); [NSTimer scheduledTimerWithTimeInterval:1repeats:YES block:^(NSTimer * _Nonnul) { NSLog(@"timer"); }]; }]; thread.name = @"Test"; [thread start];
DLThread.m
裏只寫了以下代碼
-(void)dealloc{ NSLog(@"線程銷燬了"); }
運行上面的代碼,發現timer
並無打印,說明子線程裏開啓timer
沒成功,而後添加了代碼運行當前線程的runloop,以下所示:
DLThread *thread = [[DLThread alloc] initWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"timer"); }]; [[NSRunLoop currentRunLoop] run]; }]; thread.name = @"Test"; [thread start];
發現timer
一直在打印了,這裏證實了兩個結論: timer
的運行是和runloop
有關的,子線程的runloop
是須要手動開啓的
那麼如何中止timer
呢?新增了一個標記值isStopping
用來退出線程
DLThread *thread = [[DLThread alloc] initWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"timer"); if(self.isStopping){ [NSThread exit]; } }]; [[NSRunLoop currentRunLoop] run]; }]; thread.name = @"Test"; [thread start];
運行發現,在線程銷燬後,timer
也中止了,這裏側面證實了上面的結論二: runloop
是在線程結束時銷燬的
在runloop源碼裏須要探索的:
struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // used for CFRunLoopWakeUp 內核向該端口發送消息能夠喚醒runloop Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; //RunLoop對應的線程 uint32_t _winthread; // CFMutableSetRef _commonModes; //存儲的是字符串,記錄全部標記爲common的mode CFMutableSetRef _commonModeItems;//存儲全部commonMode的item(source、timer、observer) CFRunLoopModeRef _currentMode; //當前運行的mode CFMutableSetRef _modes; //存儲的是CFRunLoopModeRef struct _block_item *_blocks_head; //doblocks的時候用到 struct _block_item *_blocks_tail; CFTypeRef _counterpart; };
能夠看到,其實runloop就是一個結構體對象,裏面包含了一個線程,一個當前正在運行的mode, N個mode,N個commonMode。
runloop一次只能運行在一個model下:
struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; //mode的名稱 Boolean _stopped; //mode是否被終止 char _padding[3]; CFMutableSetRef _sources0; //sources0 CFMutableSetRef _sources1; //sources1 CFMutableArrayRef _observers; //通知 CFMutableArrayRef _timers; //定時器 CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef __CFPortSet _portSet; //保存全部須要監聽的port,好比_wakeUpPort,_timerPort都保存在這個數組中 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 */ };
一個CFRunLoopMode
對象有一個name,N個source0、N個source一、timer、observer和port,可見事件都是由Mode
在管理,而RunLoop
管理Mode
。
它們之間的關係以下圖:
mode
是容許定製的,不過至少要包含一個mode item
(source/timer/observer)。 同一個mode item
能夠被多個mode持有
蘋果公開的三種 RunLoop Mode:
還有兩種mode,只需作了解便可:
struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; ////用於標記Signaled狀態,source0只有在被標記爲Signaled狀態,纔會被處理 pthread_mutex_t _lock; CFIndex _order; /* immutable */ CFMutableBagRef _runLoops; union { CFRunLoopSourceContext version0; /* immutable, except invalidation */ CFRunLoopSourceContext1 version1; /* immutable, except invalidation */ } _context; };
CFRunloopSourceRef
是runloop的數據源抽象類對象(protocol),由源碼能夠看到共用體(union:在相同的內存位置存儲不一樣的數據類型),可見Source分爲兩類:
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, CFStringRef mode); void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode); void (*perform)(void *info); } CFRunLoopSourceContext;
source0: 處理App內部事件、APP本身負責管理(觸發)例如:UIEvent CFSocket。 打斷點基本都會看到它。
source0
是非基於Port的。只包含了一個回調(函數指針),它並不能主動觸發事件。CFRunLoopSourceSignal
(source)將這個事件標記爲待處理CFRunLoopWakeUp
來喚醒runloop,讓他處理事件自定義source實現步驟:
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode)
CFRunLoopSourceSignal
CFRunLoopWakeUp
CFRunLoopRemoveSource
CFRelease(rlp)
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_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || 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;
source1:
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 */ };
它是一個觀察者,可以監聽Runloop的狀態改變,能夠向外部報告runloop狀態的更改,框架中不少機制都由它觸發(如CAAnimation)
在CFRunloop.h
文件裏能夠看到observer監聽的狀態以下:
/* 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流程裏的observer所對應:
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 */ };
CFRunLoopTimer
是定時器,能夠在設定的時間點拋出回調CFRunLoopTimer
和NSTimer
是toll-free bridged的,能夠相互轉換CFRunLoopTimer
的封裝有三種: NSTimer,performSelector和CADisplayLink+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; + (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel; - (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
簡單總結了這三種timer,以下圖:
void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); }
CFRunLoopRun
和CFRunLoopRunInMode
都調用了CFRunLoopRunSpecific
函數
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); /// 首先根據modeName找到對應mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); /// 通知 Observers: RunLoop 即將進入 loop。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); /// 內部函數,進入loop result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); /// 通知 Observers: RunLoop 即將退出。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; }
上面的源碼是簡化代碼後的源碼,實際源碼複雜一些,根據源碼可得出以下結論:
__CFRunLoopRun
函數/// 核心函數 static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { int32_t retVal = 0; do { /// 通知 Observers: 即將處理timer事件 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); /// 通知 Observers: 即將處理Source事件 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources) /// 處理Blocks __CFRunLoopDoBlocks(rl, rlm); /// 處理sources0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); /// 處理sources0返回爲YES if (sourceHandledThisLoop) { /// 處理Blocks __CFRunLoopDoBlocks(rl, rlm); } /// 判斷有無故口消息(Source1) if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { /// 處理消息 goto handle_msg; } /// 通知 Observers: 即將進入休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); /// 等待被喚醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); /// 通知 Observers: 被喚醒,結束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); handle_msg: if (被Timer喚醒) { /// 處理Timers __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()); } else if (被GCD喚醒) { /// 處理gcd __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } else if (被Source1喚醒) { /// 被Source1喚醒,處理Source1 __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) } /// 處理block __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; }
以上是runloop
核心函數的簡寫源碼(比較清晰易懂)點擊下載runloop源碼:密碼 3kww 還有一個監聽喚醒端口消息的函數__CFRunLoopServiceMachPort
比較重要,系統內核將這個線程掛起,停留在mach_msg_trap狀態,等待接受 mach_port(用於喚醒的端口) 的消息。線程將進入休眠, 直到被其餘線程或另外一個進程的某個線程向該端口發送mach_msg消息喚醒
/** * 接收指定內核端口的消息 * * @param port 接收消息的端口 * @param buffer 消息緩衝區 * @param buffer_size 消息緩衝區大小 * @param livePort 暫且理解爲活動的端口,接收消息成功時候值爲msg->msgh_local_port,超時時爲MACH_PORT_NULL * @param timeout 超時時間,單位是ms,若是超時,則RunLoop進入休眠狀態 * * @return 接收消息成功時返回true 其餘狀況返回false */ 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(); } //經過mach_msg發送或者接收的消息都是指針, //若是直接發送或者接收消息體,會頻繁進行內存複製,損耗性能 //因此XNU使用了單一內核的方式來解決該問題,全部內核組件都共享同一個地址空間,所以傳遞消息時候只須要傳遞消息的指針 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); //接收/發送消息成功,給livePort賦值爲msgh_local_port if (MACH_MSG_SUCCESS == ret) { *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL; return true; } //MACH_RCV_TIMEOUT //超出timeout時間沒有收到消息,返回MACH_RCV_TIMED_OUT //此時釋放緩衝區,把livePort賦值爲MACH_PORT_NULL if (MACH_RCV_TIMED_OUT == ret) { if (!originalBuffer) free(msg); *buffer = NULL; *livePort = MACH_PORT_NULL; return false; } //MACH_RCV_LARGE //若是接收緩衝區過小,則將過大的消息放在隊列中,而且出錯返回MACH_RCV_TOO_LARGE, //這種狀況下,只返回消息頭,調用者能夠分配更多的內存 if (MACH_RCV_TOO_LARGE != ret) break; //此處給buffer分配更大內存 buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE); if (originalBuffer) *buffer = NULL; originalBuffer = false; *buffer = realloc(*buffer, buffer_size); } HALT; return false; }
IOKit.framework
生成一個IOHIDEvent
事件並由SpringBoard
接受,以後由mach port
轉發給須要的App進程。_UIApplicationHandleEventQueue()
進行應用內部的分發。 _UIApplicationHandleEventQueue()
會把 IOHIDEvent 處理幷包裝成 UIEvent 進行處理或分發,其中包括識別 UIGesture/處理屏幕旋轉/發送給 UIWindow 等。_UIApplicationHandleEventQueue()
識別了一個手勢時,其首先會調用 Cancel 將當前的 touchesBegin/Move/End
系列回調打斷。隨後系統將對應的 UIGestureRecognizer
標記爲待處理。BeforeWaiting
(Loop即將進入休眠) 事件,這個Observer的回調函數是 _UIGestureRecognizerUpdateObserver()
,其內部會獲取全部剛被標記爲待處理的GestureRecognizer
,並執行GestureRecognizer
的回調。UIGestureRecognizer
的變化(建立/銷燬/狀態改變)時,這個回調都會進行相應處理。UIView/CALayer的setNeedsLayout/setNeedsDisplay
方法後,這個UIView/CALayer就被標記爲待處理。BeforeWaiting
和Exit
的Observer,在他的回調函數裏會遍歷全部待處理的UIView/CALayer
來執行實際的繪製和調整,並更新UI界面。_wrapRunloopWithAutoreleasePoolHandler
Observers1
監聽Entry
事件: 優先級最高,確保在全部的回調前建立釋放池,回調內調用 _objc_autoreleasePoolPush()
建立自動釋放池Observers2
監聽BeforeWaiting
和Exit
事件: 優先級最低,保證在全部回調後釋放釋放池。BeforeWaiting
事件:調用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
釋放舊池並建立新池,Exit
事件: 調用_objc_autoreleasePoolPop()
,釋放自動釋放池給ImageView
加載圖片的方法用PerformSelector
設置當前線程的RunLoop的運行模式kCFRunLoopDefaultMode
,這樣滑動時候就不會執行加載圖片的方法 [self.imgView performSelector:@selector(setImage:) withObject:cellImg afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
+timerWihtTimerInterval...
建立timer[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]
把timer加到當前runloop,使用佔位模式runloop run/runUntilData
手動開啓子線程runloop// 得到隊列 dispatch_queue_t queue = dispatch_get_main_queue(); // 建立一個定時器(dispatch_source_t本質仍是個OC對象) self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); // 設置定時器的各類屬性(幾時開始任務,每隔多長時間執行一次) // GCD的時間參數,通常是納秒(1秒 == 10的9次方納秒) // 比當前時間晚1秒開始執行 dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); //每隔一秒執行一次 uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC); dispatch_source_set_timer(self.timer, start, interval, 0); // 設置回調 dispatch_source_set_event_handler(self.timer, ^{ NSLog(@"------------%@", [NSThread currentThread]); }); // 啓動定時器 dispatch_resume(self.timer);
dispatch_async(dispatch_get_main_queue)
使用到了RunLooplibDispatch
向主線程的Runloop
發送消息將其喚醒,並從消息中取得block,並在回調__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
裏執行這個blockNSURLConnection
時,你會傳入一個 Delegate,當調用了 [connection start]
後,這個 Delegate
就會不停收到事件回調。start
這個函數的內部會會獲取 CurrentRunLoop
,而後在其中的 DefaultMode
添加了4個 Source0
(即須要手動觸發的Source)。 CFMultiplexerSource
是負責各類 Delegate 回調的,CFHTTPCookieStorage
是處理各類 Cookie 的。com.apple.NSURLConnectionLoader
和 com.apple.CFSocket.private
。其中 CFSocket 線程是處理底層 socket 鏈接的。NSURLConnectionLoader
這個線程內部會使用 RunLoop 來接收底層 socket 的事件,並經過以前添加的 Source0 通知到上層的 Delegate。NSRunLoop *runloop = [NSRunLoop currentRunLoop]; [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runloop run];
[NSMachPort port](source1)
使runloop不退出,實際並無給這個port發消息仿照 QuartzCore/UIKit
框架的模式,實現了一套相似的界面更新的機制:即在主線程的 RunLoop 中添加一個 Observer,監聽了 kCFRunLoopBeforeWaiting
和 kCFRunLoopExit
事件,在收到回調時,遍歷全部以前放入隊列的待處理的任務,而後一一執行。
kCFRunLoopBeforeSource
和kCFRunLoopBeforeWaiting
的間隔時長超過自定義閥值則記錄堆棧信息。@implementation ViewController { UILabel *_fpsLbe; CADisplayLink *_link; NSTimeInterval _lastTime; float _fps; } - (void)startMonitoring { if (_link) { [_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; [_link invalidate]; _link = nil; } _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(fpsDisplayLinkAction:)]; [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } - (void)fpsDisplayLinkAction:(CADisplayLink *)link { if (_lastTime == 0) { _lastTime = link.timestamp; return; } self.count++; NSTimeInterval delta = link.timestamp - _lastTime; if (delta < 1) return; _lastTime = link.timestamp; _fps = _count / delta; NSLog(@"count = %d, delta = %f,_lastTime = %f, _fps = %.0f",_count, delta, _lastTime, _fps); self.count = 0; _fpsLbe.text = [NSString stringWithFormat:@"FPS:%.0f",_fps]; }
NSSetUncaughtExceptionHandler(&HandleException);
監聽異常信號SIGILL
,SIGTRAP
,SIGABRT
,SIGBUS
,SIGSEGV
,SIGFPE
CFRunLoopRef runloop = CFRunLoopGetCurrent(); CFArrayRef allModesRef = CFRunLoopCopyAllModes(runloop); while (captor.needKeepAlive) { for (NSString *mode in (__bridge NSArray *)allModesRef) { if ([mode isEqualToString:(NSString *)kCFRunLoopCommonModes]) { continue; } CFStringRef modeRef = (__bridge CFStringRef)mode; CFRunLoopRunInMode(modeRef, keepAliveReloadRenderingInterval, false); } }
能夠把本身建立的線程添加到Runloop中,作一些頻繁處理的任務,例如:檢測網絡狀態,定時上傳一些信息等。
- (void)viewDidLoad { [super viewDidLoad]; self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [self.thread start]; } - (void)run { NSLog(@"----------run----%@", [NSThread currentThread]); @autoreleasepool{ /*若是不加這句,會發現runloop建立出來就掛了,由於runloop若是沒有CFRunLoopSourceRef事件源輸入或者定時器,就會立馬消亡。 下面的方法給runloop添加一個NSport,就是添加一個事件源,也能夠添加一個定時器,或者observer,讓runloop不會掛掉*/ [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; // 方法1 ,2,3實現的效果相同,讓runloop無限期運行下去 [[NSRunLoop currentRunLoop] run]; } // 方法2 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; // 方法3 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; NSLog(@"---------"); } - (void)test { NSLog(@"----------test----%@", [NSThread currentThread]); } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO]; }
以上均爲我的對runloop的資料收集及部分理解,若有錯誤請指正,歡迎討論。
收錄:原文地址