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,經過斷點查看堆棧能夠看到調用的方法名:網絡
block應用: CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK多線程
調用timer: CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTIONapp
響應source0: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION框架
響應source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTIONsocket
GCD主隊列: CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUEasync
observer源: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION函數
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方法名,均可以用上面的這種調試方式查看一下。
[[NSRunLoop currentRunLoop] run]
)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()
進去,以下定義了它是主線程
#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()
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
可變字典
第二步使用主線程建立了一個主線程runloopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
第三步CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
把主線程和它的runloop用key-value形式保存在這個CFMutableDictionaryRef
字典容器裏
以上說明,第一次進來的時候,不論是getMainRunloop仍是get子線程的runloop,主線程的runloop老是會被建立
再看到CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
,能夠用線程
把保存在字典裏的runloop
取出來
若是字典裏沒有找到runloop
,就根據當前的子線程建立一個新的runloop
對象並保存到字典裏
最後一步if (pthread_equal(t, pthread_self())) {...}
判斷當前的線程是否是傳遞進來的線程,若是是則建立一個回調,若是線程銷燬,就銷燬當前的runloop
這裏驗證了上面的結論1和2: runloop和線程是一一對應的(字典保存)。 runloop在首次被線程獲取時建立(而且: 無論獲取的是主線程runloop仍是子線程runloop,老是會建立主線程的runloop),在線程結束時被銷燬(經過回調銷燬)
在AppDelegate
打斷點,能夠看到主線程是有調用__CFRunloopRun
方法的,因此證實了上面的結論三: 主線程是默認開啓runloop
的 ![]user-gold-cdn.xitu.io/2019/10/16/…) 測試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。
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:
由runloop和 Mach port管理,Mach port驅動,包含一個 mach_port和一個回調(函數指針),被用於經過內核和其餘線程相互發送消息。
它可以主動喚醒RunLoop(由操做系統內核進行管理,例如: CFMachPort,CFMessagePort)
還容許實現本身的Source,但通常不會這麼作
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進程。
蘋果註冊了一個 Source1 來接受系統事件,經過回調函數觸發Source0(因此Event其實是基於Source0)的,調用_UIApplicationHandleEventQueue()
進行應用內部的分發。 _UIApplicationHandleEventQueue()
會把 IOHIDEvent 處理幷包裝成 UIEvent 進行處理或分發,其中包括識別 UIGesture/處理屏幕旋轉/發送給 UIWindow 等。
當上面的 _UIApplicationHandleEventQueue()
識別了一個手勢時,其首先會調用 Cancel 將當前的 touchesBegin/Move/End
系列回調打斷。隨後系統將對應的 UIGestureRecognizer
標記爲待處理。
蘋果註冊了一個 Observer 監測 BeforeWaiting
(Loop即將進入休眠) 事件,這個Observer的回調函數是 _UIGestureRecognizerUpdateObserver()
,其內部會獲取全部剛被標記爲待處理的GestureRecognizer
,並執行GestureRecognizer
的回調。
當有 UIGestureRecognizer
的變化(建立/銷燬/狀態改變)時,這個回調都會進行相應處理。
當UI發生改變時(Frame變化,UIView/CALayer的結構變化)時,或手動調用了UIView/CALayer的setNeedsLayout/setNeedsDisplay
方法後,這個UIView/CALayer就被標記爲待處理。
蘋果註冊了一個用來監聽BeforeWaiting
和Exit
的Observer,在他的回調函數裏會遍歷全部待處理的UIView/CALayer
來執行實際的繪製和調整,並更新UI界面。
主線程Runloop註冊了兩個Observers,其回調都是_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)
使用到了RunLoop
libDispatch
向主線程的Runloop
發送消息將其喚醒,並從消息中取得block,並在回調__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
裏執行這個block
使用 NSURLConnection
時,你會傳入一個 Delegate,當調用了 [connection start]
後,這個 Delegate
就會不停收到事件回調。
start
這個函數的內部會會獲取 CurrentRunLoop
,而後在其中的 DefaultMode
添加了4個 Source0
(即須要手動觸發的Source)。 CFMultiplexerSource
是負責各類 Delegate 回調的,CFHTTPCookieStorage
是處理各類 Cookie 的。
當開始網絡傳輸時,咱們能夠看到 NSURLConnection 建立了兩個新線程: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
事件,在收到回調時,遍歷全部以前放入隊列的待處理的任務,而後一一執行。
dispatch_semaphore_t 是一個信號量機制,信號量到達、或者 超時會繼續向下進行,不然等待,若是超時則返回的結果一定不爲0,信號量到達結果爲0。GCD信號量-dispatch_semaphore_t
經過監聽mainRunloop的狀態和信號量阻塞線程的特色來檢測卡頓,經過kCFRunLoopBeforeSource
和kCFRunLoopBeforeWaiting
的間隔時長超過自定義閥值則記錄堆棧信息。
推薦文章: RunLoop實戰:實時卡頓監控
建立CADisplayLink對象的時候會指定一個selector,把建立的CADisplayLink對象加入runloop,因此就實現了以屏幕刷新的頻率調用某個方法。
在調用的方法中計算執行的次數,用次數除以時間,就算出了FPS。
注:iOS正常刷新率爲每秒60次。
@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
回調方法內建立一個Runloop,將主線程的全部Runmode都拿過來跑,做爲應用程序主Runloop的替代。
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的資料收集及部分理解,若有錯誤請指正,歡迎討論。