RunLoop是什么旧烧?
RunLoop
是iOS
/Mac OS
開發(fā)中比較重要的知識點,它貫穿程序運行的整個過程豫缨。它是線程基礎架構的一部分看靠,是一種保障線程循環(huán)處理事件而不會退出的機制。同時也負責管理線程需要處理的事件愕秫,讓線程有事兒時忙碌慨菱,沒事兒時休眠。
每個線程都有一個關聯(lián)的RunLoop
對象戴甩,子線程的RunLoop
是需要手動開啟的符喝,主線程的RunLoop
作為應用啟動的一部分由系統(tǒng)自動開啟。
iOS
/Mac OS
提供了NSRunLoop
和CFRunLoopRef
兩個對象甜孤,幫助我們配置和管理線程的RunLoop
协饲。CFRunLoopRef
提供純C
實現(xiàn)并且線程安全的API;NSRunLoop
是基于CFRunLoopRef
封裝的面向對象的API缴川,這個API不是線程安全的茉稠。
RunLoop與線程的關系
蘋果是不建議我們自己創(chuàng)建RunLoop
對象,但是我們可以通過下列方式獲取特定線程下的RunLoop
對象:
[NSRunLoop currentRunLoop];
[NSRunLoop mainRunLoop];
//CoreFoundation
CFRunLoopGetMain();
CFRunLoopGetCurrent();
CoreFoundation
是開源的(下載地址)把夸,我們可以查看CFRunLoopRef
的關于CFRunLoopGetMain
和CFRunLoopGetCurrent
的實現(xiàn):
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
// pthread_main_thread_np() 獲取主線程
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
// pthread_self() 獲取當前線程
return _CFRunLoopGet0(pthread_self());
}
///全局`Dictionary`
static CFMutableDictionaryRef __CFRunLoops = NULL;
///訪問`Dictionary`需要的鎖
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
/// `Dictionary`不存在
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
/// 創(chuàng)建局部變量`Dictionary`
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
/// 【創(chuàng)建主線程的`RunLoop`對象】
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
/// 主線程對象的實際地址战惊,以該地址為`Key`,以`mainLoop`為`Value`存入局部變量`Dictionary`中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
///將局部變量`dict`的值 寫入全局字典`__CFRunLoops`對應的地址中
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
/// 從全局字典`__CFRunLoops`獲取線程對應的`RunLoop`
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
///如果線程對應的`RunLoop`不存在
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
/// 創(chuàng)建`RunLoop`,取線程對象的實際地址扎即,
/// 以該地址為`Key`吞获,以`newLoop`為`Value`存入全局變量`__CFRunLoops`中
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
///是當前線程
if (pthread_equal(t, pthread_self())) {
/// 將`RunLoop`對象以`__CFTSDKeyRunLoop`為`key`,儲存到線程的本地(私有)數據空間,
///析構函數為`NULL`
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
/// `CFInternal.h`定了枚舉`__CFTSDKeyRunLoop` = 10 與 `__CFTSDKeyRunLoopCntr` = 11
/// 如果線程TSD,枚舉`__CFTSDKeyRunLoopCntr` 對應`slot`未存值
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
/// 為其存值`PTHREAD_DESTRUCTOR_ITERATIONS-1`,并設置析構函數`__CFFinalizeRunLoop`谚鄙,
///此舉目的是為了:當線程銷毀時各拷,實現(xiàn)對`RunLoop`的銷毀
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
/// 如何實現(xiàn)的呢?請繼續(xù)往下看一探究竟闷营!
}
}
return loop;
}
///上述代碼的`_CFGetTSD`與`_CFSetTSD`的實現(xiàn)如下:
///TSD: Thread Specific Data
typedef struct __CFTSDTable {
uint32_t destructorCount;
///`uintptr_t` 存儲指針的烤黍,無符號整數類型,
/// 數組個數為`CF_TSD_MAX_SLOTS`
uintptr_t data[CF_TSD_MAX_SLOTS];
tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;
// For the use of CF and Foundation only
CF_EXPORT void *_CFGetTSD(uint32_t slot) {
// Get or initialize a thread local storage,It is created on demand
__CFTSDTable *table = __CFTSDGetTable();
//...
uintptr_t *slots = (uintptr_t *)(table->data);
return (void *)slots[slot];
}
// For the use of CF and Foundation only
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, tsdDestructor destructor) {
/// Get or initialize a thread local storage,It is created on demand
__CFTSDTable *table = __CFTSDGetTable();
///...
void *oldVal = (void *)table->data[slot];
///...
table->data[slot] = (uintptr_t)newVal;
///析構函數關聯(lián)
table->destructors[slot] = destructor;
return oldVal;
}
// Get or initialize a thread local storage. It is created on demand.
static __CFTSDTable *__CFTSDGetTable() {
/// 通過`CF_TSD_KEY`獲取線程對應數據
__CFTSDTable *table = (__CFTSDTable *)__CFTSDGetSpecific();
// Make sure we're not setting data again after destruction.
if (table == CF_TSD_BAD_PTR) {
return NULL;
}
// Create table on demand
if (!table) {
// This memory is freed in the finalize function
table = (__CFTSDTable *)calloc(1, sizeof(__CFTSDTable));
// Windows and Linux have created the table already, we need to initialize it here for other platforms. On Windows, the cleanup function is called by DllMain when a thread exits. On Linux the destructor is set at init time.
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
///初始化一個鍵`CF_TSD_KEY`傻盟,并關聯(lián)析構函數
/// `CF_TSD_KEY` = 55 速蕊,在不同線程該`Key`值可以共享,但此`Key`對應的值卻是不同的。
///每個線程都會有自己的`tsd`娘赴,它們共用`CF_TSD_KEY`這個`key`
/// 線程銷毀時蘋果系統(tǒng)會調用:
/// `_pthread_exit` -> `_pthread_tsd_cleanup` ->
///`_pthread_tsd_cleanup_new`->`_pthread_tsd_cleanup_key`
///當線程銷毀時规哲,會調用關聯(lián)的析構函數`__CFTSDFinalize`,保證線程對應的私有數據也能銷毀
///具體可參照函數: _pthread_tsd_cleanup_key
/// 函數實現(xiàn)[細節(jié)](https://github.com/apple/darwin-libpthread/blob/main/src/pthread_tsd.c)
pthread_key_init_np(CF_TSD_KEY, __CFTSDFinalize);
#endif
// 為`CF_TSD_KEY`指定需要存儲的數據
__CFTSDSetSpecific(table);
}
return table;
}
///銷毀線程對應的TSD
static void __CFTSDFinalize(void *arg) {
///...
__CFTSDTable *table = (__CFTSDTable *)arg;
///遍歷所有插槽 比如存`RunLoop`的`__CFTSDKeyRunLoop`诽表,也有`__CFTSDKeyRunLoopCntr`的
for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) {
if (table->data[i] && table->destructors[i]) {
uintptr_t old = table->data[I];
table->data[i] = (uintptr_t)NULL;
//遍歷到i= 11 =`__CFTSDKeyRunLoopCntr`時,調用`__CFFinalizeRunLoop`唉锌,釋放`RunLoop`
table->destructors[i]((void *)(old));
}
}
if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) { // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
free(table);
///...
__CFTSDSetSpecific(CF_TSD_BAD_PTR);
return;
}
}
_CFGetTSD
和_CFSetTSD
源碼查看可前往此處隅肥。
總結:
-
RunLoop
與線程之間是一一對應的 - 當線程需要獲取對應的
RunLoop
時,才會創(chuàng)建RunLoop
對象 - 線程銷毀的時候會銷毀
RunLoop
對象
RunLoop的相關類
CoreFoundation
中與RunLoop
有關的5
個結構體:
CFRunLoopRef //runLoop對象
CFRunLoopModeRef //runLoop運行的模式
CFRunLoopTimerRef// 基于時間的觸發(fā)器
CFRunLoopSourceRef//事件源袄简,source0:自定義事件輸入源 和 source1 :基于mach內核端口的事件源
CFRunLoopObserverRef //用于監(jiān)聽runLoop運行狀態(tài)的觀察者
它們之間的關系如下:
具體可通過打印[NSRunLoop currentRunLoop]
查看腥放,也可通過查看CFRunLoopRef
和CFRunLoopModeRef
的結構定義。兩者的結構定義如下:
///CFRunLoop.c
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
//...
CFStringRef _name;
//...
CFMutableSetRef _sources0; // <Set>
CFMutableSetRef _sources1;// <Set>
CFMutableArrayRef _observers; // <Array>
CFMutableArrayRef _timers; // <Array>
//...
};
///CFRunLoop.h 類型重命名
typedef struct __CFRunLoop * CFRunLoopRef;
///CFRunLoop.c 結構體
struct __CFRunLoop {
//..
CFMutableSetRef _commonModes; // <Set> String UITrackingRunLoopMode/kCFRunLoopDefaultMode
CFMutableSetRef _commonModeItems;// <Set> observer/source/timer
CFRunLoopModeRef _currentMode; //當前運行的mode
CFMutableSetRef _modes; //內置的modes;
//...
};
RunLoop的模式
每次運行RunLoop
都需要指定一個Mode
绿语,該Mode
會被設置為_currentMode
秃症,只有與該Mode
關聯(lián)的輸入源source0
、source1
才能被處理吕粹,同樣的伍纫,監(jiān)聽RunLoop
的運行,只有與該Mode
關聯(lián)的observers
才能收到通知昂芜。
///`RunLoop`指定`Mode`運行
CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
程序在運行的過程中,會處理基于時間的赔蒲、系統(tǒng)的泌神、用戶的事件,這些事件在程序運行期間有著不同的優(yōu)先級舞虱,為了滿足應用層依據優(yōu)先級對這些事件的管理欢际,系統(tǒng)采用RunLoopMode
對這些事件分組,然后交由RunLoop
去管理矾兜。除了系統(tǒng)定義的默認模式和常用模式损趋,我們也可以自定義模式,但是自定義的模式中必須有關聯(lián)的事件椅寺,否則自定義模式沒有任何意義浑槽。
_commonModeItems
與_commonModes
是kCFRunLoopCommonModes (NSRunLoopCommonModes)
背后的實現(xiàn)邏輯返帕,可以理解為采用RunLoopMode
對事件進行分組后荆萤,我們又希望一些事件可以同時被多個Mode
處理镊靴,于是我們將這些事件(sources/timers/observers
)放入_commonModeItems
链韭,將需要同時處理這些事件的多個Mode
放入_commonModes
集合進行標記;當事件指定kCFRunLoopCommonModes
模式進行添加時敞峭,先會添加到_commonModeItems
中踊谋,然后將_commonModeItems
中的所有事件追加到_commonModes
中已經標記的模式下褪子。
///添加一個`Mode`到`RunLoop`的`commonMode`集合中嫌褪,一旦添加無法移除笼痛。只加不減
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);
示例:主線程默認運行在kCFRunLoopDefaultMode
下缨伊,當我們滑動ScrollView
是會切換到UITrackingRunLoopMode
刻坊,而主線程的RunLoop
的_commonModes
默認包含這兩種模式谭胚;
開發(fā)中會遇到在主線程啟動一個定時器時灾而,會受視圖滑動的影響的問題旁趟,解決辦法:
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
將定時器添加到NSRunLoop
對象的NSRunLoopCommonModes
模式下锡搜,最終定時器會被添加到kCFRunLoopDefaultMode
和UITrackingRunLoopMode
下耕餐;當然也可以自行添加到這兩個模式中蛾方。
查看CoreFoundation
中CFRunLoopAddTimer
方法的實現(xiàn)桩砰,可以更深入的理解_commonModeItems
與_commonModes
的工作原理:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
///...
if (modeName == kCFRunLoopCommonModes) { ///是否是`kCFRunLoopCommonModes`
///取`Runloop`的`_commonModes`
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
///創(chuàng)建`_commonModeItems`<Set>
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
///添加定時器到`_commonModeItems`
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) { //`_commonModes`有值
CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
///為Set集合中的每個元素都調用該方法
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {
///非`kCFRunLoopCommonModes`,先找找`runloop`的modes是否有硼莽,找不到創(chuàng)建
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
if (NULL != rlm) {
if (NULL == rlm->_timers) { ///創(chuàng)建存放`timer`的數組
CFArrayCallBacks cb = kCFTypeArrayCallBacks;
cb.equal = NULL;
rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
}
}
/// mode有了偏螺,定時器對象的modes又沒有該mode
if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
///...
if (NULL == rlt->_runLoop) {
rlt->_runLoop = rl;
} else if (rl != rlt->_runLoop) {
//...
//定時器已關聯(lián)的runloop與當前runloop不一致套像,返回
return;
}
///定時器的modes 添加該mode的名稱
CFSetAddValue(rlt->_rlModes, rlm->_name);
//采用mktimer(mach kernel timer)通過machport 和 machmsg 觸發(fā)定時器事件
__CFRepositionTimerInMode(rlm, rlt, false);
///...
}
///...
}
///..
}
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);//add source
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName); // add observer
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); // add timer
}
}
CoreFoundation
中向指定RunLoopMode
中添加和移除事件的函數有:
//Source
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
//Timer
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
//Observer
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
RunLoop的運行
當應用程序啟動的時候,主線程的RunLoop
通過UIApplicationMain
函數啟動柳譬。
通過程序啟動時,函數的調用棧制跟,發(fā)現(xiàn)調用了CFRunLoopRunSpecific
;
并且Mode
之間的切換通過LLDB
調試方式:b CFRunLoopRunSpecific
和 b __CFRunLoopRun
歼指,發(fā)現(xiàn)也會調用到該方法踩身,源碼分析如下:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
///進程檢查
CHECK_FOR_FORK();
///是否銷毀
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
///互斥鎖
__CFRunLoopLock(rl);
//從`runloop`的`modes`找到`modeName`對應的`mode`琼娘,找不到也不創(chuàng)建
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
///如果`currentMode`是空的,則返回`kCFRunLoopRunFinished`
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`存儲當前rl的狀態(tài)
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
///`CurrentMode`的狀態(tài)為`kCFRunLoopEntry`時石景,
///通過`__CFRunLoopDoObservers`通知當前`Mode`對應的觀察者
/// _observerMask 設置的是rlo需要監(jiān)聽的狀態(tài)
///1.runloop處于`kCFRunLoopEntry`潮孽,通知runloopmode->observers往史,runloop即將進入
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
///`RunLoop`真正的運行邏輯
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
///`CurrentMode`的狀態(tài)為`kCFRunLoopExit`時挨决,
///通過`__CFRunLoopDoObservers`通知當前`Mode`對應的觀察者
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
///`RunLoop`運行的核心原理
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
/// TSR: time since repair
uint64_t startTSR = mach_absolute_time();
// 判斷`RunLoop`或`rlm->_stopped`是否已經停止脖祈,停止則執(zhí)行`return kCFRunLoopRunStopped`盖高。
///...
///聲明 mach_port,當(主線程的消息分發(fā)隊列是安全的&當前rl是主線程的rl&rlm->name in rl-> commonModes)撞蚕,存放與主線程(主隊列)的runLoop關聯(lián)的`mach_port`甥厦,處理`runloop`內核事件
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();
/// MacOS系統(tǒng)下矫渔,設置與Mode關聯(lián)的隊列對應的端口號(定時器隊列)
#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);
///...
}
#endif
/// 判斷參數`seconds`,決定`RunLoop`的運行時長油够,當(seconds>0&&seconds<=TIMER_INTERVAL_LIMIT),開啟GCD定時器石咬,其余情況 立即超時 和 超時不限
dispatch_source_t timeout_timer = NULL;
///...
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
///...
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
///...
dispatch_resume(timeout_timer);
Boolean didDispatchPortLastTime = true;
///開啟do-While死循環(huán) 當retVal != 0 時 停止循環(huán)
int32_t retVal = 0;
do {
///...
////聲明msg_buffer數組
uint8_t msg_buffer[3 * 1024];
///...
///rlm等待接收來自mach消息的mach_port集合
__CFPortSet waitSet = rlm->_portSet;
///取消rl忽略喚醒的設置删性,使其能接收喚醒消息
__CFRunLoopUnsetIgnoreWakeUps(rl);
///2.runloop處于`kCFRunLoopBeforeTimers`蹬挺,通知runloopmode->observers巴帮,runloop即將觸發(fā)timer回調
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
///3.runloop處于`kCFRunLoopBeforeSources`榕茧,通知runloopmode->observers,runloop即將觸發(fā)Source0(非mach_port)回調
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
///執(zhí)行runloop通過`struct _block_item *_blocks_head蜻拨、_blocks_tail`加入runloop的block;
///最終調用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__`
__CFRunLoopDoBlocks(rl, rlm);
/// 4. 執(zhí)行自定義的`source0`事件官觅,最終調用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__`
// `stopAfterHandle`A flag indicating whether the run loop should exit after processing one source
/// 如果rl立即超時或者source0已經處理完畢(rl退出)
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {///source0處理完畢咱圆,再次執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
///主線程`runloop`的mach_port有效且不是首次分配
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
///...
msg = (mach_msg_header_t *)msg_buffer;
///5.`thread`開啟`for(;;)`循環(huán)序苏,等待围来,
///通過`mach_msg`等待從rl的`dispatchPort`獲取信息监透,如果成功獲取胀蛮,則跳轉處理source1粪狼。
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
///處理msg
goto handle_msg;
}
///....
}
didDispatchPortLastTime = false;
///如果rl沒有退出 && 處于`kCFRunLoopBeforeWaiting`狀態(tài)
///6.runloop處于`kCFRunLoopBeforeWaiting`狡刘,通知runloopmode->observers颓帝,runloop即將進入休眠(sleep)
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
///設置runloop的狀態(tài)為sleep购城,此處設置1
///Bit 0 of the base reserved bits is used for stopped state
///Bit 1 of the base reserved bits is used for sleeping state
///Bit 2 of the base reserved bits is used for deallocating state
__CFRunLoopSetSleeping(rl);
///加入rlm的waitset中
__CFPortSetInsert(dispatchPort, waitSet);
///...
///設置rl休眠開始的時間,rl退出為0 否則為當前絕對時間
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
///...
///7. 通過`__CFRunLoopServiceMachPort`執(zhí)行`if(TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); }
///讓線程進入休眠侮攀,等待被`mach_msg`函數喚醒
msg = (mach_msg_header_t *)msg_buffer;
///參數超時時間為`TIMEOUT_INFINITY`觸發(fā)rl的sleep,(rlm的portSet) poll = false 標識rl 未停止,未超時 waitSet 還有定時器port
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
///macOS會循環(huán)執(zhí)行rlm->queue中事件畦贸,直到all done
///...
///rl被喚醒薄坏,計算rl的休眠時間
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
///從rlm的waitSet(portSet)中移除
__CFPortSetRemove(dispatchPort, waitSet);
///rl已被喚醒,故設置rl忽略喚醒消息
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
///取消rl的sleeping狀態(tài)
__CFRunLoopUnsetSleeping(rl);
///如果rl沒有退出 && 處于`kCFRunLoopAfterWaiting`狀態(tài)
///8.runloop處于`kCFRunLoopAfterWaiting`沈善,通知runloopmode->observers瞳脓,runloop即將被喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
///收到來自mach_port的消息會跳轉此處執(zhí)行
handle_msg:;
///rl已被喚醒劫侧,故設置rl忽略喚醒消息
__CFRunLoopSetIgnoreWakeUps(rl);
///....
///9.被喚醒處理事件
///`__CFRunLoopServiceMachPort`調用`mach_msg`成功烧栋,會設置`livePort`的值為消息來源的端口
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();///// livePort為空,do nothing
// handle nothing
} else if (livePort == rl->_wakeUpPort) {/// 通過調用`CFRunLoopWakeUp`函數喚醒rl
CFRUNLOOP_WAKEUP_FOR_WAKEUP();//
// do nothing on Mac OS
}
/// 9.1 被定時器喚醒魔吐,處理定時器事件
///被GCD Timer喚醒
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
///如果未處理酬姆,重設下次觸發(fā)時間
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
///被MK Timer喚醒
#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())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
///9.2 處理dispatch到mainQueue的block事件
else if (livePort == dispatchPort) {
/// DISPATCH 喚醒 runloop
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
///線程Data以`__CFTSDKeyIsInGCDMainQ`為key 存 6
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
///`_dispatch_main_queue_callback_4CF`,處理msg
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
///線程Data以`__CFTSDKeyIsInGCDMainQ`為key 存 0,用來在函數開始時判斷`libdispatchQSafe`的值立美。
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
///..
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
///9.3 被基于mach_port的source1喚醒建蹄,處理此事件
CFRUNLOOP_WAKEUP_FOR_SOURCE();
///...
/// 從rlm->_portToV1SourceMap的字典中针贬,取出Source1事件
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;
///處理Source1桦他,調用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__`
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {///處理完source1圆仔,如果需要回復消息坪郭,則執(zhí)行消息回復
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
///...
#endif
}
///...
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
////執(zhí)行一下加入runloop的blocks
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {///source處理完畢&處理完畢需要停止runloop
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {///已經超時
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {///通過`CFRunLoopStop`函數設置rl狀態(tài)為STOPPED
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {///runLoopMode已經停止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { ///rkm為空嗦锐,
///沒有一個timer或source0或source1 或者rl沒有block需要執(zhí)行,并且不是主隊列
retVal = kCFRunLoopRunFinished;
}
//...
} while (0 == retVal);
///...
return retVal;
}
RunLoop
運行函數內部是一個do-while
循環(huán)碳默,讓線程持續(xù)運行嘱根,接收事件,處理事件柔逼;RunLoop
定義了一些狀態(tài)愉适,當它在特定RunLoopMode
下運行時,可以向該Mode
下注冊的觀察者發(fā)送消息癌蓖;
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),///進入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1),///RunLoop即將觸發(fā)定時器事件
kCFRunLoopBeforeSources = (1UL << 2),///RunLoop即將處理Source事件
kCFRunLoopBeforeWaiting = (1UL << 5),///RunLoop即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),///RunLoop即將被喚醒,但尚未開始處理喚醒它的事件
kCFRunLoopExit = (1UL << 7),///退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
蘋果文檔中總結了RunLoop
運行時的事件的執(zhí)行序列用僧,大致如下:
- 通知觀察者糟港,
RunLoop
即將進入 - 通知觀察者秸抚,
RunLoop
即將觸發(fā)Timer
- 通知觀察者
耸别,RunLoop
即將處理Source0
(非mach_port
) - 觸發(fā)任何準備觸發(fā)的非基于端口的
Source0
輸入源 - 如果基于
mach_port
的輸入源Source1
已經就緒等待觸發(fā),則跳轉第9步處理Source1
. - 通知觀察者省有,
RunLoop
即將進入休眠 - 讓線程進入休眠,直到發(fā)生一下事件之一:
- 基于
mach_port
的輸入源Source1
發(fā)生舷蟀; - 定時器觸發(fā);
-
RunLoop
設置的timeout
生效匈子,運行即將結束; -
RunLoop
被顯式喚醒其徙,調用CFRunLoopWakeUp
;
- 基于
- 通知觀察者唾那,
RunLoop
即將被喚醒 - 喚醒后,處理待處理的事件:
- 用戶定義的定時器啟動昌罩,跳轉第2步,處理定時器事件轨功,重新開始循環(huán)(
2 ~ 9
) - 處理基于端口的輸入源,傳遞收到的消息羡滑。
-
RunLoop
被顯式喚醒但還沒超時柒昏,跳轉第2步,重新開始循環(huán)(2 ~ 9
)
- 用戶定義的定時器啟動昌罩,跳轉第2步,處理定時器事件轨功,重新開始循環(huán)(
- 通知觀察者有梆,
RunLoop
退出
RunLoop
退出時,運行函數會返回下列枚舉值:
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
kCFRunLoopRunFinished = 1,
kCFRunLoopRunStopped = 2,
kCFRunLoopRunTimedOut = 3,
kCFRunLoopRunHandledSource = 4
};
-
Source
處理完畢并且需要立即停止runloop
時,返回kCFRunLoopRunHandledSource
陨囊,退出RunLoop
; -
RunLoop
設置的timeout
生效夹攒,返回kCFRunLoopRunTimedOut
蜘醋,退出`RunLoop; - 顯式調用
CFRunLoopStop
函數,設置RunLoop
狀態(tài)為STOPPED
咏尝,返回kCFRunLoopRunStopped
压语,退出RunLoop
; -
RunLoop
運行的Mode
是停止狀態(tài)啸罢,返回kCFRunLoopRunStopped
胎食,退出RunLoop
; -
RunLoop
運行的Mode
為空扰才,沒有timer
或source0
或source1
,或者runloop
沒有需要執(zhí)行的block
返回kCFRunLoopRunFinished
厕怜,退出RunLoop
;
最后再通過一張圖衩匣,總結下RunLoop
內部運行邏輯,大致如下:
RunLoop的應用
Oberserver
CoreFoundation
中Observer
的結構:
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount; ///被添加到Rl幾次
CFOptionFlags _activities;//需要觀察RL哪些狀態(tài)
CFIndex _order;///狀態(tài)事件觸發(fā)時粥航,依此通知的觀察者琅捏,值越小優(yōu)先級越高
CFRunLoopObserverCallBack _callout; //狀態(tài)事件觸發(fā)時的回調
CFRunLoopObserverContext _context;// 上下文
};
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
觀察者,可以被添加到RunLoop
的多個Mode
下递雀,同一個Mode
下可以有多個Oberserver
柄延,當事件觸發(fā)時,觀察者是依據_order
值從小到大的順序進行事件回調的缀程。
示例: 創(chuàng)建滑動視圖并為主線程RunLoop
的kCFRunLoopCommonModes (NSRunLoopCommonModes)
添加Observer
拦焚,觀察RunLoop
的切換。
///設置觀察者
- (void)addObserverForMainRunLoop {
/*
UITrackingRunLoopMode,GSEventReceiveRunLoopMode,
kCFRunLoopDefaultMode,kCFRunLoopCommonModes
*/
void *info = (__bridge_retained void *)self;
CFRunLoopObserverContext context = {0,info,NULL,NULL,NULL};
//一個優(yōu)先級索引杠输,指示處理運行循環(huán)觀察者的順序赎败。在給定的運行循環(huán)模式下,當多個運行循環(huán)觀察者被調度在同一活動階段時蠢甲,觀察者按此參數的遞增順序進行處理僵刮。傳遞 0,除非有理由不這樣
CFRunLoopObserverRef changeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault ,
kCFRunLoopAllActivities,
YES,
0,
&_runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), changeObserver, kCFRunLoopCommonModes);
CFRelease(changeObserver);
}
///回調函數
void _runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
///獲取mode名稱
NSString* mode = (__bridge NSString*)CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
if (info) {//橋接info為oc對象
}
switch (activity) {
//do somthing...
}
}
Source0
CoreFoundation
中Source
的結構:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; ///同observer
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; //source0
CFRunLoopSourceContext1 version1; //source1
} _context;
};
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, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
typedef struct {
///...同`CFRunLoopSourceContext`前7個屬性
#if TARGET_OS_OSX || TARGET_OS_IPHONE
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
///...
#endif
} CFRunLoopSourceContext1;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
蘋果系統(tǒng)定義了一些API
,底層是基于Source0
實現(xiàn)的:
///子線程->主線程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
///主線程->子線程鹦牛,
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
不過需要注意當調用主線程->子線程序系列方法時搞糕,務必要保證子線程的RunLoop
是開啟的,否則不會生效曼追。
示例: 我們基于Souce0
簡單模仿下performSelector
從主線程發(fā)送消息給子線程窍仰。
///1.開啟子線程
_subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程的方法中,添加一個`Souce0`事件礼殊,并開啟`RL`
- (void)subthreadOperation {
///3.保存子線程的runloop
_subRunLoop = CFRunLoopGetCurrent();
///4.創(chuàng)建&添加source0
void *info = (__bridge_retained void*)self;
CFRunLoopSourceContext context = {0,info,NULL,NULL,NULL,NULL,NULL,&schedule,&cancel,&perform};
_source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
///運行RL
[[NSRunLoop currentRunLoop] run];
}
///與Source0相關的回調
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
///source0已加入子線程的runloop中
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
///source0已從子線程的runloop中移除
}
void perform(void *info) {
///橋接info,獲取來自主線程的消息
}
///5.主線程觸發(fā)Source0事件
- (void)buttonAction:(id)sender {
//5.1 觸發(fā)source
CFRunLoopSourceSignal(_source);
///5.2 喚醒runLoop
CFRunLoopWakeUp(_subRunLoop);
}
///6.移除Source0,退出子線程RL
Boolean contain = CFRunLoopContainsSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
if (contain) {
//6.1移除source
CFRunLoopRemoveSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
///6.2停止runLoop
CFRunLoopStop(_subRunLoop);
}
總結: 所謂Souce0
只不過是被包裝的帶有上下文的函數驹吮,需要主動觸發(fā),這個函數才會被執(zhí)行晶伦。
Source1
Sorce1
是基于mach_port
的事件碟狞,它是內核事件,蘋果系統(tǒng)的內核是XNU
混合內核婚陪,包括了Mach
內核和BSD
內核族沃,BSD
主要提供在Mach
之上標準化的API
,Mach
才是核心,負責線程與進程管理脆淹、虛擬內存管理常空、進程通信與消息傳遞、任務調度等盖溺。
基于Source1
的事件傳遞漓糙,主要依托于內核接口:
///System Trap / Function — Sends and receives a message using the same mes- sage buffer
mach_msg_return_t mach_msg(mach_msg_header_t *msg, mach_msg_option_t option, mach_msg_size_t send_size, mach_msg_size_t rcv_size, mach_port_name_t rcv_name, mach_msg_timeout_t timeout, mach_port_name_t notify)
這個方法底層會基于硬件異常:陷阱(trap
)實現(xiàn)事件傳遞。陷阱最終的用途咐柜,是在用戶程序和內核之間提供一個像過程一樣的接口兼蜈,稱為系統(tǒng)調用
macOS
系統(tǒng)中攘残,可以使用基于mach_port
的Source1
實現(xiàn)進程通信拙友。
示例: 創(chuàng)建一個子線程,通過Source1
建立主線程與子線程信道歼郭,實現(xiàn)雙向通信遗契。
///1.開啟子線程
_subthread = [[NSThread alloc]initWithTarget:self selector:@selector(launchThreadWithPort:) object:nil];
///2.子線程的方法中,添加一個`Souce1`事件病曾,并開啟`RL`
- (void)launchThreadWithPort:(NSPort*)port {
@autoreleasepool {
///3.創(chuàng)建&添加Source1
void *info = (__bridge_retained void*)self;
CFMessagePortContext portcontext = {0,info,NULL,NULL,NULL};
Boolean shouldFreeInfo;
CFMessagePortRef mach_port = CFMessagePortCreateLocal(kCFAllocatorDefault, CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("com.qishare.sub_mach_port")), &_messagePortCallBack, &portcontext, &shouldFreeInfo);
///保存端口牍蜂,建立雙向信道
_subPort = mach_port;
if (mach_port != NULL) {
CFRunLoopSourceRef source1 = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mach_port, 0);
if (source1 != NULL) {
CFRunLoopAddSource(CFRunLoopGetCurrent(), source1, kCFRunLoopDefaultMode);
CFRunLoopRun();
CFRelease(source1);
CFRelease(mach_port);
}
}
}
}
///3.1`Source1`的回調函數
CFDataRef _messagePortCallBack(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {
///橋接info獲取oc對象...
const UInt8 *buffer = CFDataGetBytePtr(data);
CFIndex index = CFDataGetLength(data);
CFStringRef messageref = CFStringCreateWithBytes(kCFAllocatorDefault, buffer, index, kCFStringEncodingUTF8, false);
NSString *message = (__bridge_transfer NSString*)messageref;
NSString *tip = msgid == 1002 ? @"主線程" : @"子線程";
NSLog(@"%@:%@,收到數據:%@", tip,[NSThread currentThread],message);
return NULL;
}
///5.消息發(fā)送:子線程->主線程
[self performSelector:@selector(sendMsgToMainThread) onThread:_subthread withObject:nil waitUntilDone:NO];
///5.1構建消息 10002 代表 主線程->子線程
NSData *data = [@"你好泰涂,我來自子線程??" dataUsingEncoding:NSUTF8StringEncoding];
CFDataRef msgData = (__bridge_retained CFDataRef)data;
///5.2發(fā)送消息
CFMessagePortSendRequest(_mainPort, 1002, msgData, 0.1, 0.0, NULL, NULL);
CFRelease(msgData);
///6.消息發(fā)送:主線程->子線程
CFStringRef message = CFSTR("你好鲫竞,我來自主線程??");
CFDataRef outData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
///發(fā)送消息, 10001 代表 主線程->子線程
CFMessagePortSendRequest(_subPort, 1001, outData, 0.1, 0.0, NULL, NULL);
///釋放資源
CFRelease(outData);
CFRelease(message);
Timer
CoreFoundation
中timer
的結構:
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 */
};
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
蘋果系統(tǒng)中定義的延遲調用API
逼蒙,底層便是基于Timer
實現(xiàn)的:
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
示例1: 創(chuàng)建子線程開啟一個CoreFoundation
的timer
:
///1.開啟子線程
_subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程下創(chuàng)建&添加timer
- (void)subthreadOperation {
///保存子線程`RL`
_subRunLoop = CFRunLoopGetCurrent();
@autoreleasepool {
__weak typeof(self)weakSelf = self;
CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
///定時器事件
});
_cftimer = timer;
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
CFRunLoopRun();
}
///停止時調用
NSLog(@"RunLoop:我要走向毀滅从绘,不要攔我呀!??");
}
///3.停止定時器&子線程的RL
- (void)stopCFTimerLoop {
///3.1移除`timer`
CFRunLoopRemoveTimer(_subRunLoop, _cftimer, kCFRunLoopDefaultMode);
///3.2停止RL
CFRunLoopStop(_subRunLoop);
CFRelease(_cftimer);
}
示例2: 創(chuàng)建子線程開啟一個NSFoundation
的timer
:
///1.開啟子線程
_subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程下創(chuàng)建&添加timer
- (void)subthreadOperation {
if (_timer) {
[_timer invalidate];
_timer = nil;
} else {
_timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
///定時器事件
}];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
///MARK: 定時器停止時是牢,子線程Runloop退出的思考僵井?
///[[NSRunLoop currentRunLoop]run];
///建議使用這種方式
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@"RunLoop:我要走向毀滅,你不要攔我呀驳棱!??");
}
}
///3.停止定時器&子線程的RL
- (void)stopTimerLoop {
///This method is the only way to remove a timer from an NSRunLoop object.
[_timer invalidate];
///停止
CFRunLoopStop(_subRunLoop);
}
示例2開啟子線程的RunLoop
采用[[NSRunLoop currentRunLoop]run]
的方式批什,在不調用[_timer invalidate]
的情況下,RunLoop
是無法退出的社搅,而runMode:beforeDate:
是可以的驻债。這種方式可以保證我們在RunLoop
中有其他事件源時并且未移除的情況下,能退出RunLoop
形葬。
總結一下就是runMode:beforeDate:
在不移除事件的情況下却汉,能顯式退出,而[[NSRunLoop currentRunLoop]run]
在不移除事件的情況下荷并,不能顯式退出合砂。
子線程保活
子線程保活翩伪,本質就是開啟子線程的RunLoop
微猖。但開啟子線程的RunLoop
前,必須要保證RunLoop
中至少有個Timer
缘屹、Souce0
或Source1
凛剥。
最簡單的保活方式:
///1.開啟子線程
_subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程下創(chuàng)建&添加timer
- (void)subthreadOperation {
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
_shouldKeepRunning = YES;
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (_shouldKeepRunning);
}
///3.停止
- (void)stopLoop {
CFRunLoopStop(_subRunLoop);
_shouldKeepRunning = NO;
}
參考資料
https://blog.ibireme.com/2015/05/18/runloop/
https://github.com/apple/darwin-libpthread/blob/main/src/pthread_tsd.c
https://opensource.apple.com/source/CF/CF-1153.18/CFPlatform.c.auto.html