什麼是Runloop?
Runloop不僅僅是一個運行循環(do-while循環),也是提供了一個入口函數的對象,消息機制處理模式。運行循環從兩種不同類型的源接收事件。
輸入源提供異步事件,通常是來自另一個線程或來自不同應用程序的消息。定時器源提供同步事件,發生在預定時間或重複間隔。
兩種類型的源都使用特定於應用程序的處理程序例程來處理事件。除了處理輸入源之外,Runloop還會生成有關Runloop行爲的通知。
已註冊的運行循環觀察器可以接收這些通知並使用它們在線程上執行其他處理。
執行代碼查看下主運行循環的部分信息:
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
NSLog(@"%@", mainRunloop);
打印結果,裏面有port源、modes、items等,items有很多實體(CFRunLoopSource,CFRunLoopObserver等),打印省略N行
<CFRunLoop 0x6000014c8300 [0x1034faae8]>{wakeup port = 0x2207, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x60000268cb40 [0x1034faae8]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x1068ca070 [0x1034faae8]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x10350ced8 [0x1034faae8]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x600002680ab0 [0x1034faae8]>{type = mutable set, count = 13,
entries =>
0 : <CFRunLoopSource 0x600001dc4a80 [0x1034faae8]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10b77e2bb)}}
3 : <CFRunLoopSource 0x600001dc8e40 [0x1034faae8]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600002680840, callout = __handleHIDEventFetcherDrain (0x1060e0842)}}
... // 省略N行
Runloop對象
Foundation:NSRunLoop
[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
Core Foundation:CFRunLoopRef
CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
Runloop的作用:
- Runloop可以保持程序的持續運行;
- 處理APP中的各種事件(比如觸摸,定時器,performSelector);
- 節省cup資源、提供程序的性能(需要執行事務就執行,不需要就休眠);
Runloop的應用:
- block應用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
- 調用timer:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
- 響應source0:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
- 響應source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
- GCD主隊列:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
- observer源:CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
Runloop與線程的關係:一一對應
Runloop與線程源碼分析
可以先去官方下載源碼進行分析;通過主線程獲取main
#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()
#endif
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內部調用:通過一個全局可變字典CFMutableDictionaryRef,__CFRunLoopCreate(pthread_main_thread_np())
創建mainLoop,對CFMutableDictionaryRef進行setValue,pthread_main_thread_np()線程的指針會指向當前的mainLoop,從這裏就可以看出,runLoop是基於線程創建的並且runLoop和線程是以key-value的形式一一對應的。當然CFDictionaryGetValue通過當前的__CFRunLoops,關聯pthreadPointer(t)的指針,獲取到當前的loop,都可以證明runloop和線程是一一對應的關係。
__CFSpinLock(&loopsLock);
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);
Runloop與線程代碼實現
程序啓動創建了一個子線程,在子線程內添加了一個定時器timer,並開啓子線程的runLoop,開始打印hello word
,當點擊屏幕時退出子線程停止打印。
- (void)viewDidLoad {
[super viewDidLoad];
// 主運行循環
// CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// NSLog(@"%@", mainRunloop);
self.isStopping = NO;
NSThread *customThread = [[NSThread alloc] initWithBlock:^{
NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"hello word");
if (self.isStopping) {
[NSThread exit];
}
}];
[[NSRunLoop currentRunLoop] run];
}];
customThread.name = @"customThread";
[customThread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.isStopping = YES;
}
打印結果:
<NSThread: 0x600001fb21c0>{number = 3, name = customThread}---customThread
hello word
hello word
hello word
hello word
hello word
hello word
hello word
TIP:
項目啓動,通過isStopping變量來控制當前線程,線程控制runloop,runloop控制timer。注意子線程runloop默認不開啓。timer依賴於runloop。
Runloop源碼分析
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopMode loopMode = CFRunLoopCopyCurrentMode(runLoop);
NSLog(@"mode == %@",loopMode);
CFArrayRef modeArray= CFRunLoopCopyAllModes(runLoop);
NSLog(@"modeArray == %@",modeArray);
CFRunLoopRef源碼分析
Runloop是利用線程創建的CFRunLoopRef類型,通過源碼定位,看到__CFRunLoop是一個結構體,裏面包含了_base、_lock、_wakeUpPort(激活port)、_commonModes、_commonModeItems、_modes等等,默認mode爲kCFRunLoopDefaultMode類型
typedef CFStringRef CFRunLoopMode CF_EXTENSIBLE_STRING_ENUM;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
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;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
CFRunLoopMode源碼分析
一個runLoop可以包含很多種Mode,CFRunLoopMode也是一個結構體,其中包含_sources0、_sources1、_observers、_timers等等
RunLoop的五種運行模式:
- kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
- UIInitializationRunLoopMode: 在剛啓動 App 時第進入的第一個 Mode,啓動完成後就不再使用
- GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
- kCFRunLoopCommonModes: 這是一個佔位用的Mode,作爲標記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,並不是一種真正的Mode
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
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 */
};
其中的items通過CFRunLoopAddSource
、CFRunLoopAddObserver
、CFRunLoopAddTimer
來添加CFRunLoopSourceRef、CFRunLoopObserverRef、CFRunLoopTimerRef。
RunLoop 結構
經過源碼,我們發現,CFRunLoop和線程是一一對應的,一個CFRunLoop對應多個CFRunLoopMode,一個CFRunLoopMode對應多個CFRunLoopSource、CFRunLoopObserver、CFRunLoopTimer。
RunLoop和Obsever的關係
Obsever就是觀察者,能夠監聽RunLoop的狀態改變,創建這個觀察者,再通過CFRunLoopAddObserver
把觀察者添加到runloop中,runLoopObserverCallBack
來監聽狀態的變化。
CFRunLoopObserverRef:
- (void)cfObseverDemo{
CFRunLoopObserverContext context = {
0,
((__bridge void *)self),
NULL,
NULL,
NULL
};
CFRunLoopRef rlp = CFRunLoopGetCurrent();
/**
參數一:用於分配對象的內存
參數二:你關注的事件
kCFRunLoopEntry=(1<<0),
kCFRunLoopBeforeTimers=(1<<1),
kCFRunLoopBeforeSources=(1<<2),
kCFRunLoopBeforeWaiting=(1<<5),
kCFRunLoopAfterWaiting=(1<<6),
kCFRunLoopExit=(1<<7),
kCFRunLoopAllActivities=0x0FFFFFFFU
參數三:CFRunLoopObserver是否循環調用
參數四:CFRunLoopObserver的優先級 當在Runloop同一運行階段中有多個CFRunLoopObserver 正常情況下使用0
參數五:回調,比如觸發事件,我就會來到這裏
參數六:上下文記錄信息
*/
CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, runLoopObserverCallBack, &context);
CFRunLoopAddObserver(rlp, observerRef, kCFRunLoopDefaultMode);
}
void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
NSLog(@"%lu-%@",activity,info);
}
進入源碼,runloop就是一個do-while循環,再次進入CFRunLoopRunSpecific
方法,如果監聽到有進入狀態或者退出狀態改變則執行__CFRunLoopDoObservers
,其餘的進入__CFRunLoopRun
方法。
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 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);
// 如果沒找到 || mode中沒有註冊任何事件,則就此停止,不進入循環
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);
// 取上一次運行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
// 如果本次mode和上次的mode一致
rl->_currentMode = currentMode;
// 初始化一個result爲kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 通知 Observers: RunLoop 即將進入 loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知 Observers: RunLoop 即將退出。
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
進入__CFRunLoopRun
方法,其內部有Observers監聽timer、source0、source1。
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//獲取系統啓動後的CPU運行時間,用於控制超時時間
uint64_t startTSR = mach_absolute_time();
// 判斷當前runloop的狀態是否關閉
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
return kCFRunLoopRunStopped;
rlm->_stopped = false;
}
//mach端口,在內核中,消息在端口之間傳遞。 初始爲0
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)));
//如果在主線程 && runloop是主線程的runloop && 該mode是commonMode,則給mach端口賦值爲主線程收發消息的端口
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) {
//mode賦值爲dispatch端口_dispatch_runloop_root_queue_perform_4CF
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
__CFRunLoopRun
部分源碼,接下來進入do-while循環,先初始化一個存放內核消息的緩衝池,獲取所有需要監聽的port,設置RunLoop爲可以被喚醒狀態,判斷是否有timer、source0、source1回調。如果有timer則通知 Observers: RunLoop 即將觸發 Timer 回調。如果有source0則通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調,執行被加入的block。RunLoop 觸發 Source0 (非port) 回調,再執行被加入的block。如果有 Source1 (基於port) 處於 ready 狀態,直接處理這個 Source1 然後跳轉去處理消息。例如一個Timer 到時間了,觸發這個Timer的回調。處理完後再次進入__CFArmNextTimerInMode
查看是否有其他的timer。如果沒有事務需要處理則通知 Observers: RunLoop 的線程即將進入休眠(sleep),此時會進入一個內循環,線程進入休眠狀態mach_msg_trap(比如我們在斷點調試的時候),直到收到新消息才跳出該循環,繼續執行run loop。比如監聽到了事務基於 port 的Source 的事件、Timer 到時間了、RunLoop 自身的超時時間到了或者被其他什麼調用者手動喚醒則喚醒。
//標誌位默認爲true
Boolean didDispatchPortLastTime = true;
//記錄最後runloop狀態,用於return
int32_t retVal = 0;
do {
//初始化一個存放內核消息的緩衝池
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;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
//取所有需要監聽的port
__CFPortSet waitSet = rlm->_portSet;
//設置RunLoop爲可以被喚醒狀態
__CFRunLoopUnsetIgnoreWakeUps(rl);
/// 2. 通知 Observers: RunLoop 即將觸發 Timer 回調。
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources)
/// 3. 通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調。
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
/// 執行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
/// 4. RunLoop 觸發 Source0 (非port) 回調。
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
/// 執行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果沒有Sources0事件處理 並且 沒有超時,poll爲false
//如果有Sources0事件處理 或者 超時,poll都爲true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do..whil循環不會走該分支,因爲didDispatchPortLastTime初始化是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//從緩衝區讀取消息
msg = (mach_msg_header_t *)msg_buffer;
/// 5. 如果有 Source1 (基於port) 處於 ready 狀態,直接處理這個 Source1 然後跳轉去處理消息。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果接收到了消息的話,前往第9步開始處理msg
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
// ...
RunLoop和Timer的關係
首先timer要加入到runLoop的其中一個mode中,也就是加入到當前mode的items中;在runLoopRun的時候,執行doBlock,然後while循環items,block調用。
NSTimer:
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"hell timer -- %@",[[NSRunLoop currentRunLoop] currentMode]);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
timer的底層CFRunLoopTimerRef:
- (void)cfTimerDemo{
CFRunLoopTimerContext context = {
0,
((__bridge void *)self),
NULL,
NULL,
NULL
};
CFRunLoopRef rlp = CFRunLoopGetCurrent();
/**
參數一:用於分配對象的內存
參數二:在什麼是觸發 (距離現在)
參數三:每隔多少時間觸發一次
參數四:未來參數
參數五:CFRunLoopObserver的優先級 當在Runloop同一運行階段中有多個CFRunLoopObserver 正常情況下使用0
參數六:回調,比如觸發事件,就會執行
參數七:上下文記錄信息
*/
CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, runLoopTimerCallBack, &context);
CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);
}
void runLoopTimerCallBack(CFRunLoopTimerRef timer, void *info){
NSLog(@"%@---%@",timer,info);
}
我們再次查找RunLoop的addTimer方法CFRunLoopAddTimer
(當然也有AddObserver、AddSource等),先判斷kCFRunLoopCommonModes是否相同,如果不是則進行查找,其中CFSetAddValue
把CFRunLoopTimerRef對象保存在items中,CFSetApplyFunction
再把剛加進來的item儲存到commonModes中。
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
} else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
// ...省略N行代碼
把item添加到modes中後,__CFRunLoopRun
方法有個重要的方法CFRunLoopDoBlocks
,rl是runLoop,rlm是runLoopMode,把runLoopMode傳給runLoop中,檢查將執行哪個事務
__CFRunLoopDoBlocks(rl, rlm);
// -----------------------------------
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
if (!rl->_blocks_head) return false;
if (!rlm || !rlm->_name) return false;
Boolean did = false;
struct _block_item *head = rl->_blocks_head;
struct _block_item *tail = rl->_blocks_tail;
rl->_blocks_head = NULL;
rl->_blocks_tail = NULL;
CFSetRef commonModes = rl->_commonModes;
CFStringRef curMode = rlm->_name;
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
struct _block_item *prev = NULL;
struct _block_item *item = head;
while (item) {
struct _block_item *curr = item;
item = item->_next;
Boolean doit = false;
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));
}
if (!doit) prev = curr;
if (doit) {
if (prev) prev->_next = item;
if (curr == head) head = item;
if (curr == tail) tail = prev;
void (^block)(void) = curr->_block;
CFRelease(curr->_mode);
free(curr);
if (doit) {
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
did = true;
}
Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
if (head) {
tail->_next = rl->_blocks_head;
rl->_blocks_head = head;
if (!rl->_blocks_tail) rl->_blocks_tail = tail;
}
return did;
}
__CFRunLoopDoBlocks中通過鏈表遍歷item, 判斷當前的runLoopMode和加入的runLoopMode或者CFRunLoopCommonModes是否相同,執行doit
,進入__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
,執行block
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
if (block) {
block();
}
getpid(); // thwart tail-call optimization
}
舉個例子,把定時器添加到RunLoop中,timer加入的runLoopMode類型NSDefaultRunLoopMode
,和當前runLoopMode的類型(runLoopMode可以切換,比如默認kCFRunLoopDefaultMode類型,滑動的時候UITrackingRunLoopMode,啓動時UIInitializationRunLoopMode)比較,默認情況下執行timer,當頁面滑動的時候,當前runLoopMode的類型自動切換到UITrackingRunLoopMode
,因此timer失效,停止滑動時,當前runLoopMode的類型切換到NSDefaultRunLoopMode
,timer恢復。當然了,如果我們把timer加入到UITrackingRunLoopMode模式時,那麼只有在滑動的時候才執行。如果想在默認情況下和滑動的時候都執行,就要把timer加入到佔位模式NSRunLoopCommonModes中,NSRunLoopCommonModes相當於Mode集合,這樣就可以在兩個模式下都執行了,這就是爲什麼定時器不準的原因與解決辦法。
RunLoop和Source的關係
__CFRunLoopSource也是一個結構體,其中有一個union屬性,它包含了version0和version1,也就是Source0(CFRunLoopSourceContext)和Source1(CFRunLoopSourceContext1)。進入源碼
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
Source0分析
Source0是用來處理APP內部事件、APP自己負責管理,比如UIevent。
調用底層:因爲source0只包含一個回調(函數指針)它並不能主動觸發事件;CFRunLoopSourceSignal(source)將這個事件標記爲待處理;CFRunLoopWakeUp來喚醒runloop,讓他處理事件。首先創建一個Source0並添加到當前的runLoop中,執行信號,標記待處理CFRunLoopSourceSignal,再喚醒runloop去處理CFRunLoopWakeUp,通過CFRunLoopRemoveSource
來取消移除源,CFRelease(rlp)。打印結果會顯示準備執行
和取消了,終止了!!!
,如果註釋掉CFRunLoopRemoveSource
,則會打印準備執行
和執行啦
。
- (void)source0Demo{
CFRunLoopSourceContext context = {
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
schedule,
cancel,
perform,
};
/**
參數一:傳遞NULL或kCFAllocatorDefault以使用當前默認分配器。
參數二:優先級索引,指示處理運行循環源的順序。這裏傳0爲了的就是自主回調
參數三:爲運行循環源保存上下文信息的結構
*/
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
CFRunLoopRef rlp = CFRunLoopGetCurrent();
// source --> runloop 指定了mode 那麼此時我們source就進入待緒狀態
CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode);
// 一個執行信號
CFRunLoopSourceSignal(source0);
// 喚醒 run loop 防止沉睡狀態
CFRunLoopWakeUp(rlp);
// 取消 移除
CFRunLoopRemoveSource(rlp, source0, kCFRunLoopDefaultMode);
CFRelease(rlp);
}
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
NSLog(@"準備執行");
}
void perform(void *info){
NSLog(@"執行啦");
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
NSLog(@"取消了,終止了!!!");
}
Source1分析
Source1被用於通過內核和其他線程相互發送消息。
__調用底層:__Source1包含一個 mach_port和一個回調(函數指針)
當然了,線程間的通訊除了可以通過以下方式:
// 主線 -- 子線程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@", [NSThread currentThread]); // 3
NSString *str;
dispatch_async(dispatch_get_main_queue(), ^{
// 1
NSLog(@"%@", [NSThread currentThread]);
});
});
還可以通過更加底層、更加接近內核的NSPort方式,NSPort是source1類型,通過addPort添加到runLoop中去。再添加子線程,子線程中再加入port。
- (void)setupPort{
self.mainThreadPort = [NSPort port];
self.mainThreadPort.delegate = self;
// port - source1 -- runloop
[[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
[self task];
}
- (void) task {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
self.subThreadPort = [NSPort port];
self.subThreadPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
}
子線程給主線程發送消息響應。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
[self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
當NSPort對象接收到端口消息時,會調起handlePortMessage
,官方文檔如下解釋:
When an
NSPort
object receives a port message, it forwards the message to its delegate in ahandle<wbr>Mach<wbr>Message:
orhandle<wbr>Port<wbr>Message:
message. The delegate should implement only one of these methods to process the incoming message in whatever form desired.handle<wbr>Mach<wbr>Message:
provides a message as a raw Mach message beginning with amsg_header_t
structure.handle<wbr>Port<wbr>Message:
provides a message as anNSPort<wbr>Message
object, which is an object-oriented wrapper for a Mach message. If a delegate has not been set, theNSPort
object handles the message itself.
端口接收到消息後會打印message內部屬性:localPort
、components
、remotePort
等,睡眠一秒後,主線程再向子線程發送消息。
- (void)handlePortMessage:(id)message {
NSLog(@"%@", [NSThread currentThread]); // 3 1
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([message class], &count);
for (int i = 0; i<count; i++) {
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
NSLog(@"%@",name);
}
sleep(1);
if (![[NSThread currentThread] isMainThread]) {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"woard" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
[self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
}
}
該文章爲記錄本人的學習路程,希望能夠幫助大家,也歡迎大家點贊留言交流!!!文章地址:https://www.jianshu.com/p/9cb4edc0670d