你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里
转载请注明出处 http://www.jianshu.com/p/f0a7fb39f79c
本系列文章主要讲解iOS中多线程的使用,包括:NSThread、GCD、NSOperation以及RunLoop的使用方法详解,本系列文章不涉及基础的线程/进程、同步/异步、阻塞/非阻塞、串行/并行,这些基础概念,有不明白的读者还请自行查阅。本系列文章将分以下几篇文章进行讲解,读者可按需查阅。
- iOS多线程——你要知道的NSThread都在这里
- iOS多线程——你要知道的GCD都在这里
- iOS多线程——你要知道的NSOperation都在这里
- iOS多线程——你要知道的RunLoop都在这里
- iOS多线程——RunLoop与GCD、AutoreleasePool
RunLoop的执行者 __CFRunLoopRun源码解析
在前一篇文章中由于篇幅问题没有具体分析__CFRunLoopRun
函数的源码,这一部分先来看一下真正执行RunLoop
循环的源码:
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) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
/*
如果当前RunLoop是主线程关联的RunLoop
dispatchPort被置为GCD主队列的端口号
*/
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();
#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);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
//使用GCD的dispatch_source_t实现RunLoop的超时机制
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 = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
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 ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
Boolean didDispatchPortLastTime = true;
//do-while循环的判断条件,retVal为0则保持循环
int32_t retVal = 0;
do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
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
__CFPortSet waitSet = rlm->_portSet;
__CFRunLoopUnsetIgnoreWakeUps(rl);
//调用__CFRunLoopDoObservers函数,通知监听器即将处理Timer
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//调用__CFRunLoopDoObservers函数,通知监听器即将处理Source
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
/*
执行source0(非基于端口)事件
sourceHandledThisLoop在执行了source0事件后置为true在一个循环结束后会使用
__CFRunLoopRun函数有一个形参stopAfterHandle,如果其为true
并且sourceHandledThisLoop也为true在一个循环结束后就会退出RunLoop
*/
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
/*
判断是否有source1(基于端口的)事件需要处理
如果有则跳转到handle_msg标签处执行
*/
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//获取到内核的消息
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
//调用__CFRunLoopDoObservers函数通知监听器,RunLoop即将进入睡眠
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);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//休眠的循环,等到source1事件、Timer时间到达、外部唤醒
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
#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.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
// 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);
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//前述代码中如果有source1事件则跳转到该标签进行处理,唤醒后也会走这里,处理Timer和source1事件
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
//在windows平台下的条件编译的代码
#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;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
//没有source1事件需要处理
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
}
//被显示唤醒
else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//Timer时间到达需要进行处理
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
//同上处理Timer
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
/*
处理主线程的主队列端口事件
比如使用dispatch_async(dispatch_get_main_queue(),^{
});
函数,调用主队列来执行任务,这个任务就在这里处理
*/
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
//真正执行上述任务的函数
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
}
//处理source1事件
else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
// 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 || DEPLOYMENT_TARGET_EMBEDDED_MINI
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
}
// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
//执行添加进来的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;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
由于作者水平有限,有些部分的代码也没看太懂,只能注释到这里,但总体的执行流程都和前一篇文章中讲解的一样,从这个源码中也能看到不少我们需要掌握的部分。
RunLoop与GCD
从上面的源码中可以看到,在执行RunLoop
的循环中使用了GCD
的dispatch_source_t
来实现其超时机制。
还有一个比较重要的地方就是GCD
中将任务提交到主线程的主队列即dispatch_get_main_queue()
时,这里的任务是由RunLoop
负责执行,从源码中可以看到,如果当前RunLoop
对象是主线程关联的,则会执行下述代码:
dispatchPort = _dispatch_get_main_queue_port_4CF();
这行代码获取了主线程主队列的端口号并赋值,接着在handle_msg
标签后的代码会判断主队列中是否有任务需要执行,如果有,则调用__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
函数进行处理。但只有主队列的任务会交由RunLoop
对象处理,其他队列的则由GCD
自行处理。
RunLoop与AutoreleasePool
关于AutoreleasePool
的原理不是本文的重点,有兴趣的读者推荐看以下两篇文章:
黑幕背后的Autorelease http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
自动释放池的前世今生 ---- 深入解析 Autoreleasepool http://www.jianshu.com/p/32265cbb2a26
当程序运行后,输出[NSRunLoop mainRunLoop]
,然后查看observers
部分的输出,可以看到有如下输出:
"<CFRunLoopObserver 0x1c412db60 [0x1b220f240]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = <_wrapRunLoopWithAutoreleasePoolHandler> (0x18ad71908), context = <CFArray 0x1c4454970 [0x1b220f240]>{type = mutable-small, count = 1, values = (\n\t0 : <0x102bf8048>\n)}}",
"<CFRunLoopObserver 0x1c412dac0 [0x1b220f240]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = <_wrapRunLoopWithAutoreleasePoolHandler> (0x18ad71908), context = <CFArray 0x1c4454970 [0x1b220f240]>{type = mutable-small, count = 1, values = (\n\t0 : <0x102bf8048>\n)}}"
首先查看activities
属性,这个值就是前一篇文章讲解的监听器监听事件的枚举值,具体值如下:
/* 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
};
其中0x1
即kCFRunLoopEntry
,监听RunLoop
对象进入循环的事件,0xa0
即kCFRunLoopBeforeWaiting | kCFRunLoopExit
,监听RunLoop
即将进入休眠和RunLoop
对象退出循环的事件。
程序运行后产生的两个CFRunLoopObserver
一个监听RunLoop
对象进入循环的事件,执行回调函数_wrapRunLoopWithAutoreleasePoolHandler
,并且优先级order
为-2147483647
即32位整数的最小值,保证了它的优先级最高。在回调内会调用_objc_autoreleasePoolPush
函数来创建AutoreleasePool
,由于它的优先级最高,所以能够保证自动释放池在其他回调执行前得到创建。
另一个监听器监听RunLoop
对象进入休眠和退出循环的事件,回调函数同样是_wrapRunLoopWithAutoreleasePoolHandler
,而优先级为2147483647
即32位整数的最大值,保证它的优先级最低。对于监听进入休眠状态时回调函数内首先会调用_objc_autoreleasePoolPop
函数来释放AutoreleasePool
然后使用_objc_autoreleasePoolPush
函数重新创建一个自动释放池。优先级最低保证了释放操作是在其他所有回调执行之后发生。
main
函数就是被@autoreleasepool
包围着,所以在主线程中创建的任何对象都会及时被释放。
通过上面的讲解,不难发现,需要进入到runloop
中才会释放旧的自动释放池然后创建新的自动释放池,那如果程序在处理一个比较耗时且占用内存较大的任务时,在没有任何事件产生的情况下是不会进入到runloop
中,那些本应该立即释放的局部变量就不会被释放,程序就会被占用过多的内存,如下栗子:
- (void)btnClickedHandler
{
NSArray *urls = ;
for (NSURL *url in urls) {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
}
加上之前的监听runloop
状态的代码,可以发现,在for循环结束前都不会改变runloop
的状态,runloop
一直处于休眠的状态,所以for循环大量创建的局部变量不会得到释放,就会占用过多的内存,直到runloop
改变状态,此时就需要手动添加@autoreleasepool
来手动创建和释放自动释放池:
- (void)btnClickedHandler
{
NSArray *urls = ;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
}
}
上面的栗子来源于官方文档,具体运行结果,读者可自行实验,并在xcode
中观察内存占用情况。
RunLoop实现常驻内存的线程
直接看代码:
+ (void)entryPoint
{
//设置当前线程名为MyThread
[[NSThread currentThread] setName:@"MyThread"];
//获取NSRunLoop对象,第一次获取不存在时系统会创建一个
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
/*
添加一个Source1事件的监听端口
RunLoop对象会一直监听这个端口,由于这个端口不会有任何事件到来所以不会产生影响
监听模式是默认模式,可以修改为Common
*/
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//启动RunLoop
[runloop run];
}
+ (NSThread *)longTermThread
{
//静态变量保存常驻内存的线程对象
static NSThread *longTermThread = nil;
//使用GCD dispatch_once 在应用生命周期只执行一次常驻线程的创建工作
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//创建一个线程对象,并执行entryPoint方法
longTermThread = [[NSThread alloc] initWithTarget:self selector:@selector(entryPoint) object:nil];
//启动线程,启动后就会执行entryPoint方法
[longTermThread start];
});
return longTermThread;
}
- (void)viewDidLoad
{
//获取这个常驻内存的线程
NSThread *thread = [ViewController longTermThread];
//在该线程上提交任务
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];
}
上面的栗子很好理解,主要利用了一个source1
事件的监听,由于Mode
的Source/Observer/Timer
中的Observer
不为空,所以RunLoop
不会退出循环,能够常驻内存。
备注
由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。