iOS多线程基础之RunLoop与GCD、AutoreleasePool解析
iOS多线程基础之RunLoop与GCD、AutoreleasePool解析本系列文章主要讲解iOS中多线程的使用,包括:NSThread、GCD、NSOperation以及RunLoop的使用方法详解,本系列文章不涉及基础的线程/进程、同步/异步、阻塞/非阻塞、串行/并行,这些基础概念,有不明白的读者还请自行查阅。
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); // 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); // 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
从上面的源码中可以看到,在执行
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
RunLoop与AutoreleasePool
当程序运行后,输出
[NSRunLoop mainRunLoop],然后查看
observers部分的输出,可以看到有如下输出:
"{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = <_wrapRunLoopWithAutoreleasePoolHandler> (0x18ad71908), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x102bf8048>\n)}}", "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = <_wrapRunLoopWithAutoreleasePoolHandler> (0x18ad71908), context = {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)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不会退出循环,能够常驻内存。