最近看了一下
深入研究 Runloop 與線程绷詹剩活
想到幾個(gè)問題又研究了一下。
為什么使用 RunLoop 會(huì)造成內(nèi)存泄漏
這個(gè)問題還需要看怎樣定義內(nèi)存泄漏。如果是像偎血,循環(huán)引用這種霸株,出乎程序員意料的方式造成內(nèi)存得不到釋放的情況才叫做內(nèi)存泄漏雕沉,那么 RunLoop 不應(yīng)該被稱為內(nèi)存泄漏。
RunLoop 占用內(nèi)存不釋放去件,還是跟單例對(duì)象占用內(nèi)存得不到釋放歸到一類比較好坡椒。因?yàn)椋琑unLoop 就是這樣設(shè)計(jì)的——一旦開啟 RunLoop 要跟隨程序結(jié)束而結(jié)束尤溜,這點(diǎn)是程序員應(yīng)該明確的倔叼。
RunLoop 為什么需要一直占用內(nèi)存
使用文檔上的著名圖片來解釋,NSRunLoop 需要不斷接受另一個(gè)線程發(fā)送過來的消息宫莱,當(dāng)然不能退出嘍丈攒。既然不能退出,NSRunLoop 就會(huì)一直持有相關(guān)聯(lián)的對(duì)象授霸,導(dǎo)致內(nèi)存不能釋放巡验。另外 NSThread 啟動(dòng)子線程也是需要占用內(nèi)存的,而且不小碘耳,我記得是主線程1m显设,子線程0.5m。
接受消息的具體工作過程辛辨,網(wǎng)絡(luò)上有很多捕捂,就不再 Copy 過來了,想了解的可以看看這篇愉阎。
淺談NSRunloop工作原理和相關(guān)應(yīng)用
RunLoop 的啟動(dòng)
文檔上說了 RunLoop 不能手動(dòng)初始化绞蹦,它跟 NSThread 是一起的,屬于 NSThread 的 associateObject榜旦,如果要獲取一個(gè) NSThread 的 RunLoop幽七,必須調(diào)用 [NSRunLoop currentRunLoop]。
NSRunLoop 有三種啟動(dòng)方式:
- Unconditionally
- With a set time limit
- In a particular mode
對(duì)應(yīng)的方法分別為: - run
- runUntilDate
- runMode:beforeDate:
參照文檔以及 RunLoop 的源碼源碼在此
runMode:beforeDate:
這個(gè)方法才是啟動(dòng)一次 RunLoop! 為什么這樣說溅呢,稍后再解釋澡屡。
runUntilDate:
這個(gè)方法,會(huì)循環(huán)調(diào)用 runMode:beforeDate: 直到達(dá)到參數(shù) NSDate 所指定的時(shí)間咐旧,也就是超時(shí)時(shí)間驶鹉。
run
這個(gè)方法,可以看做是
[runloop runUntilDate:[NSDate distantFuture]];
RunLoop 啟動(dòng)后如何接收消息
RunLoop 啟動(dòng)后铣墨,必須注冊(cè) source(翻譯成消息源好不好)室埋。source 有兩種,一種是 NSPort 及其子類,另一種是 NSTimer姚淆。
使用 NSRunLoop 的消息機(jī)制進(jìn)行線程通信孕蝉,就依靠 NSPort 及其子類。關(guān)鍵方法在這里:
- (void)setup{
NSLog(@"%@",[[NSRunLoop currentRunLoop] currentMode]);
//創(chuàng)建并啟動(dòng)一個(gè) thread
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadTest) object:nil];
[self.thread setName:@"Test Thread"];
[self.thread start];
//向 RunLoop 發(fā)送消息的簡便方法腌逢,系統(tǒng)會(huì)將消息傳遞到指定的 SEL 里面
[self performSelector:@selector(receiveMsg) onThread:self.thread withObject:nil waitUntilDone:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//直接去的子線程 RunLoop 的一個(gè) port降淮,并向其發(fā)送消息,這個(gè)是比較底層的 NSPort 方式進(jìn)行線程通信
[self.port sendBeforeDate:[NSDate date] components:[@[[@"hello" dataUsingEncoding:NSUTF8StringEncoding]] mutableCopy] from:nil reserved:0];
});
}
- (void)threadTest{
NSLog(@"%@",@"child thread start");
//threadTest 這個(gè)方法是在 Test Thread 這個(gè)線程里面運(yùn)行的搏讶。
NSLog(@"%@",[NSThread currentThread]);
//獲取這個(gè)線程的 RunLoop 并讓他運(yùn)行起來
NSRunLoop* runloop = [NSRunLoop currentRunLoop];
self.port = [[NSMachPort alloc]init];
self.port.delegate = self;
[runloop addPort:self.port forMode:NSRunLoopCommonModes];
//約等于runUntilDate:[NSDate distantFuture]
[runloop run];
}
- (void)receiveMsg{
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",@"receive msg");
}
#pragma NSMachPortDelegate
- (void)handleMachMessage:(void *)msg{
NSLog(@"handle message thread:%@",[NSThread currentThread]);
}
- 使用 run 方法啟動(dòng) NSRunLoop佳鳖,使用 [self performSelector:@selector(receiveMsg) onThread:self.thread withObject:nil waitUntilDone:NO]; 發(fā)送消息,- (void)receiveMsg接受消息打印出來的內(nèi)容:
2017-04-25 17:03:19.322693 test[2553:585069] <NSThread: 0x174c76480>{number = 5, name = Test Thread}
2017-04-25 17:03:19.322921 test[2553:585069] receive msg
- 使用 run 方法啟動(dòng) NSRunLoop 使用[self.port sendBeforeDate:[NSDate date] components:[@[[@"hello" dataUsingEncoding:NSUTF8StringEncoding]] mutableCopy] from:nil reserved:0];發(fā)送消息媒惕,- (void)handleMachMessage:(void *)msg 接受消息打印出來的內(nèi)容:
2017-04-25 17:03:20.408981 test[2553:585069] handle message thread:<NSThread: 0x174c76480>{number = 5, name = Test Thread}
上面是分別使用高層方法和底層方法進(jìn)行消息通信系吩。
RunLoop 是如何隨時(shí)隨地接收消息的。
上文說過使用 run 方法啟動(dòng) RunLoop 后吓笙,會(huì)循環(huán)調(diào)用
runMode:beforeDate:
這個(gè)方法淑玫,我想,可以把這個(gè)方法稱為 1次消息循環(huán)面睛。
下面這段代碼是 CFRunloop 源碼中的一個(gè)函數(shù)絮蒿,我猜是1次消息循環(huán)的具體實(shí)現(xiàn)。大概的工作方式是這樣的:
- 判斷這次 RunLoop 的狀態(tài)(應(yīng)該對(duì)應(yīng)文檔中 NSRunLoop 的 Model)如果不是退出的狀態(tài)就繼續(xù)向下執(zhí)行叁鉴。
- 看看有沒有超時(shí)時(shí)間(這個(gè)時(shí)間應(yīng)該是runMode:beforeDate:這里的 date)添加一個(gè) timer土涝。
- 發(fā)送進(jìn)入 RunLoop 的消息。(每一次 RunLoop 開始前幌墓,都會(huì)發(fā)送通知)
- 如果 NSPort 中沒有消息但壮,線程進(jìn)入阻塞狀態(tài),交出 CPU常侣。
- 如果 NSPort 中有消息蜡饵,就會(huì)喚醒這個(gè) RunLoop 所在的線程,RunLoop 繼續(xù)運(yùn)行并處理這個(gè)消息胳施。
- 如果 NSPort 中還有消息溯祸,就回到3這一步(因?yàn)橛邢⑿枰幚恚€程不會(huì)進(jìn)入阻塞狀態(tài)舞肆,而是一直處理消息)
- NSPort 中消息處理完畢(或者 timer 超時(shí)時(shí)間到或者手動(dòng)退出這個(gè)消息循環(huán)焦辅,這兩種情況也會(huì)喚醒這個(gè)線程,并執(zhí)行這一步)RunLoop 退出椿胯。
這樣筷登,一次消息循環(huán)結(jié)束。
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int64_t startTSR = (int64_t)mach_absolute_time();
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
mach_port_name_t dispatchPort = MACH_PORT_NULL;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0LL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t nanos = (uint64_t)(seconds * 1000 * 1000 + 1) * 1000;
dispatch_source_set_timer(timeout_timer, dispatch_time(DISPATCH_TIME_NOW, nanos), DISPATCH_TIME_FOREVER, 0);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = INT64_MAX;
}
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
mach_msg_header_t *msg = NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
__CFPortSet waitSet = rlm->_portSet;
rl->_ignoreWakeUps = false;
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(rl, rlm);
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0LL == timeout_context->termTSR);
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), 0)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), poll ? 0 : TIMEOUT_INFINITY);
#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
// Note: don't pass 0 for polling, or this thread will never yield the CPU.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
rl->_ignoreWakeUps = true;
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
rl->_ignoreWakeUps = true;
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
mach_port_t livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
} else
#endif
if (MACH_PORT_NULL == livePort) {
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
} else if (livePort == rlm->_timerPort) {
#if DEPLOYMENT_TARGET_WINDOWS
// We use a manual reset timer to ensure that we don't miss timers firing because the run loop did the wakeUpPort this time
// The only way to reset a timer is to reset the timer using SetWaitableTimer. We don't want it to fire again though, so we set the timeout to a large negative value. The timer may be reset again inside the timer handling code.
LARGE_INTEGER dueTime;
dueTime.QuadPart = LONG_MIN;
SetWaitableTimer(rlm->_timerPort, &dueTime, 0, NULL, NULL, FALSE);
#endif
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
} else if (livePort == dispatchPort) {
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
_dispatch_main_queue_callback_4CF(msg);
#elif DEPLOYMENT_TARGET_WINDOWS
_dispatch_main_queue_callback_4CF();
#endif
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < (int64_t)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);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
而根據(jù)源碼得知,run 和 runUntilDate 這兩個(gè)方法會(huì)不停的運(yùn)行上面這個(gè)消息循環(huán)函數(shù)從而做到一直監(jiān)聽消息哩盲。
RunLoop 怎樣才能退出呢
上面提到過前方,可以手動(dòng)結(jié)束一次消息循環(huán)狈醉,結(jié)束的函數(shù)就是這個(gè) CFRunLoopStop()。但是要注意惠险,這個(gè)函數(shù)只是結(jié)束一次消息循環(huán)舔糖,跟 runMode:beforeDate: 中 timer 超時(shí)的效果是一樣的。也就是停止等待 NSPort 傳遞消息莺匠,喚醒線程,獲取 CPU 時(shí)間片完成消息循環(huán)剩下的任務(wù)十兢。
然而 run 和 runUntilDate 這兩個(gè)方法還會(huì)啟動(dòng)下一次消息循環(huán)趣竣,這也就是用 run 方法啟動(dòng) RunLoop 后,無法 terminatel 的原因旱物。runUntileDate 會(huì)在 date 時(shí)間到達(dá)時(shí)不再啟動(dòng)新的消息循環(huán)遥缕,從而讓 RunLoop 真正退出。
只要明確了一次消息循環(huán)的過程宵呛,并且知道了 RunLoop 不斷執(zhí)行的原理是一次又一次啟動(dòng)消息循環(huán)单匣,那么,退出 RunLoop 的方式應(yīng)該明確了宝穗。
- 使用 run 啟動(dòng) RunLoop户秤,直到程序結(jié)束再退出。主線程的 RunLoop 一定是這種方式啟動(dòng)的逮矛。
- 使用 runUntileDate 啟動(dòng) RunLoop鸡号,date 時(shí)間到退出。這里的 date 會(huì)當(dāng)做參數(shù)傳到下面這個(gè)方法中须鼎,當(dāng) date 時(shí)間到鲸伴,RunLoop 也會(huì)因?yàn)槌瑫r(shí)而結(jié)束一次循環(huán)。
- 使用 runMode:beforeDate: 這種方式啟動(dòng)一次消息循環(huán)晋控,并且自己編寫代碼來控制一次消息循環(huán)結(jié)束后是否啟動(dòng)下一次消息循環(huán)汞窗。也就是類似這樣的代碼:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
- 還有一種方式,就是文檔上說過赡译,如果 RunLoop 中沒有 NSPort仲吏,就不會(huì)啟動(dòng)下一次 RunLoop 了。如果沒有 NSPort捶朵,RunLoop 進(jìn)入阻塞狀態(tài)后誰負(fù)責(zé)喚醒呢蜘矢?當(dāng)然不能啟動(dòng)了對(duì)吧。但是文檔也說了综看,這種方式結(jié)束 RunLoop循環(huán)品腹,通常情況下由于無法持有所有 NSPort 的引用而做不到從 RunLoop 中移除所有 NSPort,是不推薦的方式红碑。
注意上一段中一次 RunLoop 與 RunLoop 循環(huán)的區(qū)別舞吭,RunLoop 循環(huán)指的是一次 RunLoop 運(yùn)行結(jié)束時(shí)再啟動(dòng)一次 RunLoop泡垃,也就是 runUntileDate: 做的事情。
RunLoop 模式可以節(jié)省 CPU 的并等待消息的關(guān)鍵就在于 RunLoop 會(huì)進(jìn)入阻塞狀態(tài)等待 NSPort 的消息將它喚醒羡鸥,底層是操作系統(tǒng)最基本的功能——線程同步蔑穴。通常情況下,這種消息是操作系統(tǒng)產(chǎn)生(例如觸摸屏幕等事件)惧浴,也可以由其它線程產(chǎn)生
看到這里存和,應(yīng)該明白 NSRunLoop 的意義以及線程保活的原理了衷旅。其實(shí)就是建立在操作系統(tǒng)線程同步的基礎(chǔ)上捐腿,比較底層的一種可以節(jié)省 CPU 又能實(shí)現(xiàn)進(jìn)程間通信的一種方式。這種 RunLoop 功能在各種操作系統(tǒng)上都有類似的實(shí)現(xiàn)柿顶,比如 Android 和 Windows茄袖。
NSRunLoop 的測試
- run啟動(dòng),NSMachPort 發(fā)送消息
文檔上說了嘁锯,iOS 只支持 NSMachPort
- (void)setup{
NSLog(@"%@",[[NSRunLoop currentRunLoop] currentMode]);
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadTest) object:nil];
[self.thread setName:@"Test Thread"];
[self.thread start];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.port sendBeforeDate:[NSDate date] components:[@[[@"hello" dataUsingEncoding:NSUTF8StringEncoding]] mutableCopy] from:nil reserved:0];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.port sendBeforeDate:[NSDate date] components:[@[[@"hello" dataUsingEncoding:NSUTF8StringEncoding]] mutableCopy] from:nil reserved:0];
});
}
- (void)threadTest{
NSLog(@"%@",@"child thread start");
NSLog(@"%@",[NSThread currentThread]);
NSRunLoop* runloop = [NSRunLoop currentRunLoop];
self.port = [[NSMachPort alloc]init];
self.port.delegate = self;
[runloop addPort:self.port forMode:NSRunLoopCommonModes];
[runloop run];
}
\#pragma NSMachPortDelegate
- (void)handleMachMessage:(void *)msg{
NSLog(@"handle message thread:%@",[NSThread currentThread]);
}
輸出
2017-04-25 18:01:34.011602 test[2633:602345] kCFRunLoopDefaultMode
2017-04-25 18:01:34.013161 test[2633:604579] child thread start
2017-04-25 18:01:34.014156 test[2633:604579] <NSThread: 0x171660280>{number = 5, name = Test Thread}
2017-04-25 18:01:35.098691 test[2633:604579] handle message thread:<NSThread: 0x171660280>{number = 5, name = Test Thread}
2017-04-25 18:01:36.213378 test[2633:604579] handle message thread:<NSThread: 0x171660280>{number = 5, name = Test Thread}
1s 和2s 發(fā)送的消息都能收到宪祥。
- runUntilDate:啟動(dòng)并發(fā)送消息
還是上面的代碼
如果
[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];
RunLoop 運(yùn)行時(shí)間只有4s,收不到6s 時(shí)發(fā)送的消息家乘,如果將運(yùn)行時(shí)間改長久可以收到了蝗羊。輸出
2017-04-25 18:05:57.176821 test[2657:607056] kCFRunLoopDefaultMode
2017-04-25 18:05:57.178433 test[2657:607261] child thread start
2017-04-25 18:05:57.186123 test[2657:607261] <NSThread: 0x17106b0c0>{number = 5, name = Test Thread}
2017-04-25 18:06:00.477071 test[2657:607261] handle message thread:<NSThread: 0x17106b0c0>{number = 5, name = Test Thread}
這里需要注意的是,如果[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];時(shí)間改為5烤低,是有可能受到6s 發(fā)送的消息的肘交,這里可能是多線程運(yùn)行時(shí),線程運(yùn)行時(shí)機(jī)并不能精確確定導(dǎo)致的扑馁。
- runMode:beforeDate: 啟動(dòng)并發(fā)送消息
[runloop runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:15]];
輸出:
2017-04-25 18:11:22.310154 test[2668:609276] kCFRunLoopDefaultMode
2017-04-25 18:11:22.312849 test[2668:609542] child thread start
2017-04-25 18:11:22.314500 test[2668:609542] <NSThread: 0x1758639c0>{number = 5, name = Test Thread}
無論如何也收不到第二個(gè)消息涯呻,這是由于接受第一個(gè)消息是,RunLoop 被喚醒腻要,處理完消息后直接退出了复罐,NSThread 線程也會(huì)結(jié)束。
深入研究 Runloop 與線程毙奂遥活
http://www.cocoabuilder.com/archive/cocoa/305204-runloop-not-being-stopped-by-cfrunloopstop.html
https://opensource.apple.com/source/CF/CF-635/CFRunLoop.c.auto.html
另外效诅,NSObject 有一個(gè)方法
performSelector: onThread: withObject: waitUntilDone: modes:
waitUntilDone這個(gè)參數(shù)可以指定在子線程運(yùn)行時(shí)阻塞當(dāng)前線程。具體實(shí)現(xiàn)原理可以參考下面這兩個(gè)鏈接趟济。
這里雖然是 Linux 的實(shí)現(xiàn)乱投,但是 iOS Linux Windows 都是遵循 POSIX 標(biāo)準(zhǔn)的。
之前介紹 thread join和detach的區(qū)別但是不詳細(xì) (詳細(xì)介紹)
pthread_join和pthread_detach的用法(轉(zhuǎn))