字面意思运行循环
,它是一个对象,这个对象提供一个入口函数。
程序会进入do...while
循环,处理事件。它不是一个普通的do-while
循环,普通的do-while
会一直暂用CPU资源,runloop
在没有消息处理时,会进入休眠表面资源占用。
CFRunLoopGetMain()
:获取主运行循环。
CFRunLoopGetCurrent()
:获取当前运行循环。相关源码分析:
从获取线程RunLoop
的方法CFRunLoopGetCurrent()
进去:
CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); } 复制代码
获取RunLoop
,调用_CFRunLoopGet0
,当前线程(pthread_self()
作为参数传入。
static pthread_t kNilPthreadT = { nil, nil }; CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { //1.为Nil,设置为主线程 if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } 2.加锁,保证线程安全 __CFSpinLock(&loopsLock); 3.__CFRunLoops是CFMutableDictionaryRef类型的静态全局变量,保存线程和runloop一一对应的关系 if (!__CFRunLoops) { 4.如果__CFRunLoops为空 __CFSpinUnlock(&loopsLock); //5.创建可变字典CFMutableDictionaryRef CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); //6.通过pthread_main_thread_np()创建一个CFRunLoopRef CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); //7.通过key-value的方式,将pthread_main_thread_np()和mainLoop存入`dict` CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); //8.将dict赋值给__CFRunLoops if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFSpinLock(&loopsLock); } //9.在__CFRunLoops,线程作为key获取runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFSpinUnlock(&loopsLock); //10.不存在runloop if (!loop) { //11.创建一个loop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { //12.将创建好的newloop存储到__CFRunLoops CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; } 复制代码
__CFRunLoops
,线程为key,对应的runloop
为value
保存在__CFRunLoops
,线程和runloop是一一对应的关系.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;//commonModes下的两个mode(kCFRunloopDefaultMode和UITrackingMode) CFMutableSetRef _commonModeItems;// 在commonModes状态下运行的对象(例如Timer) CFRunLoopModeRef _currentMode;////在当前loop下运行的mode CFMutableSetRef _modes;// // 运行的所有模式(CFRunloopModeRef类) struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFTypeRef _counterpart; }; 复制代码
struct __CFRunLoopMode { CFStringRef _name; // Mode Name CFMutableSetRef _sources0; // Set CFMutableSetRef _sources1; // Set CFMutableArrayRef _observers; // Array CFMutableArrayRef _timers; // Array ... }; 复制代码
一个RunLoop
对象中可能包含多个Mode
,且每次调用 RunLoop
的主函数时,只能指定其中一个 Mode(CurrentMode)
。可重写指定并切换Mode
。主要是为了分隔开不同的 Source、Timer、Observer
,让它们之间互不影响。
RunLoop下共有五种mode:
项目中,如下如下场景:页面中有一个无限循环的banner,当用户在界面上滑动时,banner定时器不起作用。
原因:主线程的 RunLoop
里有两个 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。默认情况下是defaultMode
,但是当滑动UIScrollView
时,RunLoop
会将 mode
切换为 TrackingRunLoopMode
,这时 Timer 就不会执行。如果想在滑动的时候不让定时器失效,可以使用CommonMode
来解决。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 复制代码
CFRunLoopTimerRef 是定时源,你可以简单把它理解为NSTimer。其包含一个时间点和一个回调(函数指针)。当被加入到 RunLoop 时,RunLoop 会注册对应的时间点,当时间到时,RunLoop 会执行对应时间点的回调。NSTimer 和 performSelector:withObject:afterDelay: 都是通过其处理的。
CFRunLoopObserverRef是观察者,主要用来监听RunLoop 的状态,主要有以下几种状态。
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 }; 复制代码
Runloop
的运行从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); } 复制代码
接下来都是调用CFRunLoopRunSpecific
:
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 ) /// 1. 通知 Observers: RunLoop 即将进入 loop。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); if (currentMode->_observerMask & kCFRunLoopExit ) /// 10. 通知 Observers: RunLoop 即将退出。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; } 复制代码
进入核心代码__CFRunLoopRun
,代码太长,这里只贴出核心代码:
/// 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 } didDispatchPortLastTime = false; /// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。 if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); //设置RunLoop为休眠状态 __CFRunLoopSetSleeping(rl); 复制代码
msg = (mach_msg_header_t *)msg_buffer; /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。 /// • 一个基于 port 的Source 的事件。 /// • 一个 Timer 到时间了 /// • RunLoop 自身的超时时间到了 /// • 被其他什么调用者手动唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); 复制代码
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); 复制代码
总结:
主线程几乎所有函数都从以下六个之一的函数调起:
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimation
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
消息通知、非延迟的perform、非延迟的dispatch调用、block回调、KVO
block应用: ``` void (^block)(void) = ^{ NSLog(@"123"); }; block(); ``` 复制代码
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"hello word"); });
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
延迟的perform, 延迟dispatch调用
[self performSelector:@selector(fire) withObject:nil afterDelay:1.0]; 复制代码
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用 复制代码
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort 复制代码
runLoop
的超时时间就是使用 GCD
中的 dispatch_source_t
来实现的
执行GCD MainQueue
上的异步任务
runloop
用到了GCD
,当调用 dispatch_async(dispatch_get_main_queue(), block)
时,libDispatch
会向主线程的RunLoop
发送消息,RunLoop
会被唤醒,并从消息中取得这个 block
,并在回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE()
里执行这个 block
。但这个逻辑仅限于 dispatch
到主线程,dispatch
到其他线程仍然是由 libDispatch
处理的。
苹果在主线程 RunLoop
里注册了两个 ``Observer:
第一个Observer
监视的事件是 Entry
(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer
监视了两个事件: BeforeWaiting
(准备进入睡眠) 和 Exit
(即将退出Loop),
BeforeWaiting
(准备进入睡眠)时调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;
Exit
(即将退出Loop) 时调用 _objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
当在操作 UI
时,比如改变了 Frame
、更新了 UIView/CALayer
的层次时,或者手动调用了 UIView/CALayer
的 setNeedsLayout/setNeedsDisplay
方法后,这个 UIView/CALayer
就被标记为待处理,并被提交到一个全局的容器去。
苹果注册了一个 Observer 监听 BeforeWaiting
(即将进入休眠) 和Exit
(即将退出Loop) 事件,回调去执行。遍历所有待处理的 UIView/CAlayer
以执行实际的绘制和调整,并更新 UI
界面。
苹果注册了一个 Source1
(基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()
。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework
生成一个 IOHIDEvent
事件并由 SpringBoard
接收。SpringBoard
只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port
转发给需要的App
进程。随后苹果注册的那个 Source1
就会触发回调,并调用 _UIApplicationHandleEventQueue()
进行应用内部的分发。
_UIApplicationHandleEventQueue()
会把 IOHIDEvent
处理并包装成 UIEvent
进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。 当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
NSTimer 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。一个 NSTimer 注册到 RunLoop 后,RunLoop会为其重复的时间点注册好事件,RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差.
meInterval tolerance API_AVAILABLE(macos(10.9), ios(7.0), watchos(2.0), tvos(9.0)); 复制代码
NSTimer和performSEL方法实际上是对CFRunloopTimerRef的封装.
当调用 NSObject
的 performSelecter:afterDelay:
后,实际上其内部会创建一个 Timer
并添加到当前线程的RunLoop
中。所以如果当前线程没有 RunLoop
,则这个方法会失效。
当调用 performSelector:onThread:
时,实际上其会创建一个 Timer
加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
为了保证线程长期运转,可以在子线程中加入RunLoop
,并且给Runloop
设置item
,防止Runloop
自动退出。
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; } - (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock]; } 复制代码
所谓的卡顿一般是在主线程做了耗时操作,卡顿监测的主要原理是在主线程的RunLoop
中添加一个 observer
,检测从 即将处理Source(kCFRunLoopBeforeSources)
到 即将进入休眠 (kCFRunLoopBeforeWaiting)
花费的时间是否过长。如果花费的时间大于某一个阙值,则认为卡顿,此时可以输出对应的堆栈调用信息。