概念
RunLoop 就像他的名字一样 就是跑环 就是一个死循环 是一个可以随时休眠 随时唤醒的死循环。
一个手机App之所以能够一直运行 而且在用户点击的时候 做出反应 这些都离不开RunLoop。
iOSApp启动的时候,就会自动启动一个RunLoop,一直在循环监听着用户的各种操作,并作出反应。每一个线程都有一个RunLoop,但是,只有主线程的RunLoop 是默认开启的。
1.RunLoop是iOS消息机制的处理模式 RunLoop的主要作用控制线程的执行和休眠 在有事做的时候,使当前RunLoop的线程工作,没事做的时候让当前RunLoop的线程休息.
2.RunLoop就是一直在做循环检测,从线程start到线程的end,监测inuptSource(单击 双击等操作)同步事件 监测timeSource(计时器)同步操作。检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。
RunLoop的作用:保持程序的持续运行 处理App中的各种事件(点击 触摸) 节省CPU资源 提高程序性能(在有事做的时候,使当前RunLoop的线程工作,没事做的时候让当前RunLoop的线程休息.)
RunLoop和我们平常的开发息息相关 有很大的联系 我们使用的定时器 PerformSelector() GCD 事件响应 手势识别 界面刷新 网络请求 和AutoreleasePool 这些东西的底层 都和它相关。
iOS 中的main函数
int main(int argc, char * argv[]) { @autoreleasepool { //一旦程序启动会开启一个RunLoop 一直循环监听用户的点击事件 触摸事件 定时器事件等 并且一直不会返回。 保证程序一直运行,直到程序结束。这个默认的RunLoop就是跟主线程相关的。 int rs = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); return rs; } }
RunLoop对象
iOS中有2套API来访问和使用RunLoop
Foundation框架中的NSRunLoop
Core Foundation框架中的:CFRunLoopRef
NSRunLoop 和 CFRunLoop都代表着RunLoop对象 NSRunLoop是基于CFRunLoopRef的一层OC包装。
- (void)viewDidLoad { [super viewDidLoad]; //获取RunLoop对象 NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; CFRunLoopRef runLoopRef = CFRunLoopGetCurrent(); }
每条线程都有唯一的一个与之对应的RunLoop对象 RunLoop保存在一个全局的字典里 线程作为key RunLoop作为Value
线程在刚刚创建的时候并没有RunLoop对象 RunLoop会在第一次获取它的时候创建。([NSRunLoop currentRunLoop] 获取RunLoop的时候 如果发现不存在 会创建一个返回)
RunLoop会在线程结束的时候销毁。主线程的RunLoop已经自动获取(创建) 子线程默认没有开启RunLoop。
RunLoop相关的类
CFRunLoopRef CFRunLoopModeRef CFRunLoopSourceRef CFRunLoopTimerRef CFRunLoopObserverRef
CFRunLoopRef
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; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFTypeRef _counterpart; };
从结构体中可以看到 里面有用来保证线程安全的_lock, 有用来唤醒runLoop的端口_wakeUpPort
,有线程对象_pthread
,还有一个模式集合_modes
以及一些其他辅助的属性。
_pthread
可以看到与我们上面所说的一样,RunLoop与线程是一一对应的,这不仅体现在RunLoop的结构体中,还体现在RunLoop的构造函数中
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) { CFRunLoopRef loop = NULL; CFRunLoopModeRef rlm; uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase); loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL); if (NULL == loop) { return NULL; } (void)__CFRunLoopPushPerRunData(loop); __CFRunLoopLockInit(&loop->_lock); loop->_wakeUpPort = __CFPortAllocate(); if (CFPORT_NULL == loop->_wakeUpPort) HALT; __CFRunLoopSetIgnoreWakeUps(loop); loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode); loop->_commonModeItems = NULL; loop->_currentMode = NULL; loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); loop->_blocks_head = NULL; loop->_blocks_tail = NULL; loop->_counterpart = NULL; loop->_pthread = t; #if DEPLOYMENT_TARGET_WINDOWS loop->_winthread = GetCurrentThreadId(); #else loop->_winthread = 0; #endif rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true); if (NULL != rlm) __CFRunLoopModeUnlock(rlm); return loop; }
可以看出构造一个RunLoop对象仅需要一个pthread_t线程即可,即一个线程对应一个RunLoop。
我们上面讲解到,一个线程创建的时候 是没有对应的RunLoop的,直到我们在这个线程中获取他的RunLoop的时候,下面代码也可以证明这些
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) {//如果传入线程为空指针则默认取主线程对应的runLoop t = pthread_main_thread_np(); } __CFSpinLock(&loopsLock); if (!__CFRunLoops) {//__CFRunLoops就是一个全局字典,以下代码为如果全局字典不存在则创建全局字典,并将主线程对应的mainLoop存入字典中 __CFSpinUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFSpinLock(&loopsLock); } CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));//从全局字典中,取出对应线程的runLoop __CFSpinUnlock(&loopsLock); if (!loop) {//若对应线程的runLoop为空,则创建对应的runLoop并保存在全局字典中 CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { 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; }
可以知道 RunLoop存放在一个全局的字典中,key是线程,value就是RunLoop。 事实上我们调用currentRunLoop方法 底层就是调用的上面的方法。
_modes
我们可以看到,一个RunLoop中同时还维护着一个集合_modes。那么这个集合是做什么的呢?首先我们看一个_modes里面到底都装了些什么?
_CFRunLoopModeRef 底层也是一个结构体
struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; Boolean _stopped; char _padding[3]; CFMutableSetRef _sources0; /* CFRunLoopSourceRef */ CFMutableSetRef _sources1; /* CFRunLoopSourceRef */ CFMutableArrayRef _observers; /* CFRunLoopObserverRef */ CFMutableArrayRef _timers;/* CFRunLoopTimerRef */ CFMutableDictionaryRef _portToV1SourceMap; __CFPortSet _portSet; CFIndex _observerMask; #if USE_DISPATCH_SOURCE_FOR_TIMERS dispatch_source_t _timerSource; dispatch_queue_t _queue; Boolean _timerFired; // set to true by the source when a timer has fired Boolean _dispatchTimerArmed; #endif #if USE_MK_TIMER_TOO mach_port_t _timerPort; Boolean _mkTimerArmed; #endif #if DEPLOYMENT_TARGET_WINDOWS DWORD _msgQMask; void (*_msgPump)(void); #endif uint64_t _timerSoftDeadline; /* TSR */ uint64_t _timerHardDeadline; /* TSR */ };
有用来标志RunLoop的标志_name,有两个事件源集合_sources0 _sources1 有一组观察者_obeserver,有一组被加入到RunLoop中的_timers,还有Mode本身维护着的一个用于计时的_timerSource,_timerPort。这两个一个是GCD时钟,一个是内核的时钟。
CFRunLoopSourceRef 事件产生的地方 包含了source0和source1
_sources0 不基于端口 只包含了一个回调(函数指针) 不能主动触发事件。 使用时 需要先调用CFRunLoopSourceSignal(source),将这恶搞Source标记为待处理 然后手动调用CFRunLoopWakeUp(runloop) 来唤醒RunLoop 让其处理这个事件。比如:触摸事件 performSelector:onThread:
_sources1: 基于端口的 包含了一个mach_port和一个回调(函数指针) 用于通过内核和其他线程相互发送消息。能主动唤醒runloop 处理事件。大部分用于系统事件的处理。
CFRunLoopTimerRef 是基于时间的触发器 可以把他当作NSTimer 其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
_timers: NSTimer performSelector:withObject:afterDelay
CFRunLoopObserverRef 是观察者每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入Loop kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7), // 即将退出Loop };
Observers: 用于监听RunLoop的状态 UI刷新 autorelease pool 下面的代码就是监听RunLoop的状态
#import "ViewController.h" @interface ViewController () @end @implementation ViewController void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { switch (activity) { case kCFRunLoopEntry: NSLog(@"kCFRunLoopEntry"); break; case kCFRunLoopBeforeTimers: NSLog(@"kCFRunLoopBeforeTimers"); break; case kCFRunLoopBeforeSources: NSLog(@"kCFRunLoopBeforeSources"); break; case kCFRunLoopBeforeWaiting: NSLog(@"kCFRunLoopBeforeWaiting"); break; case kCFRunLoopAfterWaiting: NSLog(@"kCFRunLoopAfterWaiting"); break; case kCFRunLoopExit: NSLog(@"kCFRunLoopExit"); break; default: break; } } - (void)viewDidLoad { [super viewDidLoad]; //监听RunLoop的状态 CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL); // 添加Observer到RunLoop中 CFRunLoopAddObserver(CFRunLoopGetMain(), observerRef, kCFRunLoopCommonModes); // 释放 CFRelease(observerRef); //或者这样 CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopEntry - %@", mode); CFRelease(mode); break; } case kCFRunLoopExit: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopExit - %@", mode); CFRelease(mode); break; } default: break; } }); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"定时器-----------"); }]; } @end
所以我们平时写的一些代码 比如设置背景色 frame 设置文字 点击事件的处理 定时器 都被包装成sources0 sources1 timers observers提供给runloop处理。
CFRunLoopModeRef代表着RunLoop的运行模式
一个RunLoop下包含若干个Mode 每个Mode又包含若干个Sources0/Sources1/Timer/Observer RunLoop启动的时候 只能选择其中的一个Mode 作为currentMode 如果需要切换Mode 只能退出当前的RunLoop 再重新选择一个Mode进入(好处就是不同组的Source0/Source1/Timer/Observer能分割开来 互不影响 每个模式下处理不同的事情 也能保证效率(不处理其他事情))
如果Mode里没有任何Source0/Source1/Timer/Observer RunLoop会立马退出。
iOS系统中有五种Mode
KCFRunLoopDefaultMode:默认模式 主线成在这个模式下运行
UITrackingRunLoopMode: 跟踪用户交互事件(用于ScrollView追踪触摸滑动 保证洁面滑动时不受其他Mode的影响)
UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode 启动完成后就不再使用
GSEventReveiveRunLoopMode:接受系统内部事件 我们通常用不到
KCFRunLoopCommonModes:伪模式 不是一种真正的运行模式 是同步是同步Source/Timer/Observer到多个Mode中的一种解决方案
关于最后一种伪模式 其实上面RunLoop的结构体里面有一个概念叫做 '_commonModes': 一个Mode可以将自己标记为'Common'属性(通过将其ModeName添加到RunLoop的'commonModes'中)。每当RunLoop的内容发生变化时,RunLoop 都会自动将_commonModelItems里的Source/Observer/Timer同步到具有'Common'标记的所有Mode里。即被标记了'Common' 的Mode可以同步不同Mode下的Source/Observer/Timer。NSRunLoopCommonModes 中就包含了两种被标记为'Common'的Mode 即KCFRunLoopDefaultMode和UITrackingRunLoopMode。所以如果RunLoop处于这种Mode下 可以同时处理默认模式下的事情和跟踪用户的交互事件。
应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。
有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。
RunLoop 的内部逻辑
1. 通知Observer 进入RunLoop
2. 通知Observer 即将处理Timers
3. 通知Observer 即将处理sources
4. 处理Blocks
5. 处理sources0(根据处理结果 可能会再次处理blocks)
6.判断是否有sources1(如果有 进入第八步) 如果没有 进入休眠状态 等待唤醒(第七步)
7.通知Observer 开始休眠(等待消息唤醒)
8.通知Observer 结束休眠(被某个消息唤醒) (如果是Timer唤醒 处理Timer 如果是CGD唤醒 处理GCD 如果是Sources1 处理sources1)
9.处理Blocks
10. 根据上面处理的执行结果 决定如何操作(回到第二步) 或者直接退出RunLoop(通知Observer 推出RunLoop)
从源码上来看也是这样的
/* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time();//获取当前内核时间 if (__CFRunLoopIsStopped(rl)) {//如果当前runLoop或者runLoopMode为停止状态的话直接返回 __CFRunLoopUnsetStopped(rl); return kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; return kCFRunLoopRunStopped; } //判断是否是第一次在主线程中启动RunLoop,如果是且当前RunLoop为主线程的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(); #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 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 = 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 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; int32_t retVal = 0; do { 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); // 第二步,通知观察者准备开始处理Timer源事件 if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 第三步,通知观察者准备开始处理Source源事件 if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); //执行提交到runLoop中的block __CFRunLoopDoBlocks(rl, rlm); // 第四步,执行source0中的源事件 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); //如果当前source0源事件处理完成后执行提交到runLoop中的block if (sourceHandledThisLoop) { __CFRunLoopDoBlocks(rl, rlm); } //标志是否等待端口唤醒 Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); // 第五步,检测端口,如果端口有事件则跳转至handle_msg(首次执行不会进入判断,因为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; if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 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); // 标志当前runLoop为休眠状态 __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); // 第七步,进入循环开始不断的读取端口信息,如果端口有唤醒信息则唤醒当前runLoop #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI #if USE_DISPATCH_SOURCE_FOR_TIMERS 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); 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); #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); // 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); //标志当前runLoop为唤醒状态 __CFRunLoopSetIgnoreWakeUps(rl); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); // 第八步,通知观察者线程被唤醒了 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //执行端口的事件 handle_msg:; //设置此时runLoop忽略端口唤醒(保证线程安全) __CFRunLoopSetIgnoreWakeUps(rl); #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 // 第九步,处理端口事件 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 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 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 //处理有GCD提交到主线程唤醒的事件 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; } else { //处理source1唤醒的事件 CFRUNLOOP_WAKEUP_FOR_SOURCE(); // 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; // 处理Source1(基于端口的源) 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 || DEPLOYMENT_TARGET_EMBEDDED_MINI 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 < 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; }
RunLoop 进入休眠状态 CPU就不会在给这个线程分配资源,线程就会休眠(相当于线程阻塞但不会做任何事情 不是死循环的那种阻塞) 如果有消息唤醒 线程也会唤醒。
真正的节省CPU的资源。(切换到内核态 mach_msg 实现真正的线程休眠 不是用户态死循环的那种阻塞)
RunLoop 在实际中的应用
控制线程的生命周期
#import <Foundation/Foundation.h> @interface MJThread : NSThread @end #import "MJThread.h" @implementation MJThread - (void)dealloc { NSLog(@"xixixi%s", __func__); } @end #import "ViewController.h" #import "MJThread.h" @interface ViewController () @property (strong, nonatomic) MJThread *thread; @property (assign, nonatomic, getter=isStoped) BOOL stopped; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.stopped = NO; self.thread = [[MJThread alloc] initWithBlock:^{ NSLog(@"%@----begin----", [NSThread currentThread]); // 往RunLoop里面添加SourceTimerObserver [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; //使用这种方法 可以保证RunLoop对应的线程释放 //weakSelf && !weakSelf.isStoped 在dealloc 里面调用stop方法 weakSelf 会被置nil 所以不能单独使用!weakSelf.isStoped while (weakSelf && !weakSelf.isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"%@----end----", [NSThread currentThread]); // 不能使用run方法开启一个loop 除非你的这个线程需要永远保存 不被释放 // run方法无限循环的不停的调用runMode:beforeDate:方法 相当于一个死循环 // CFRunLoopStop(CFRunLoopGetCurrent());只能停止其中的一次Loop 但不影响run方法执行下一次 也就是说run方法开启一个Runloop这个RunLoop会休眠 但是线程不会释放 // 下面的代码 就是run方法的本质 // while (1) { // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; // } //[[NSRunLoop currentRunLoop] run]; }]; [self.thread start]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO]; } // 子线程需要执行的任务 - (void)test { NSLog(@"%s %@", __func__, [NSThread currentThread]); } - (IBAction)stop { // 在子线程调用stop 最后一个参数传NO 意思就是不等待stopThread执行结束 这个方法就会立即执行完毕 所以如果在dealloc中调用stop stop方法会立即结束控制器会立即释放 在执行stopThread 会出现野指针错误所以传YES比较安全 [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES]; } // 用于停止子线程的RunLoop - (void)stopThread { // 设置标记为YES self.stopped = YES; // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s %@", __func__, [NSThread currentThread]); } - (void)dealloc{ NSLog(@"%s", __func__); if (!self.isStoped) { [self stop]; } } @end
上面的代码虽然可以保证线程的生命周期 但是用起来还是很麻烦的。我们可以封装一下。
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface LFPermenantThread : NSObject //执行任务 - (void)executeTask:(void(^)(void))task; //结束一个线程 - (void)stop; @end NS_ASSUME_NONNULL_END @interface LFThread : NSThread @end #import "LFPermenantThread.h" @implementation LFThread - (void)dealloc { NSLog(@"线程销毁 %s",__func__); } @end @interface LFPermenantThread() @property (nonatomic,strong) LFThread *thread; @property (nonatomic,assign) BOOL isStoped; @end @implementation LFPermenantThread - (instancetype)init { self = [super init]; if (self) { __weak typeof(self) weakSelf = self; self.thread = [[LFThread alloc] initWithBlock:^{ //保活 [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf.isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } }]; [self.thread start]; } return self; } //- (void)run { // if (!self.thread) { // return; // } // [self.thread start]; //} - (void)stop { if (!self.thread) { return; } [self performSelector:@selector(__stop) onThread:self.thread withObject:nil waitUntilDone:YES]; } - (void)__stop { self.isStoped = YES; CFRunLoopStop(CFRunLoopGetCurrent()); self.thread = nil; } - (void)executeTask:(void (^)(void))task { if (!self.thread || !task) { return; } if (!self.thread.isExecuting) { //如果没有执行任务 [self.thread start]; } [self performSelector:@selector(__excuteTask:) onThread:self.thread withObject:task waitUntilDone:NO]; } - (void) __excuteTask:(void(^)(void))task { task(); } - (void)dealloc { [self stop]; NSLog(@"封装的对象 %s",__func__); } @end #import "ViewController.h" #import "LFPermenantThread.h" @interface ViewController () @property (nonatomic,strong) LFPermenantThread *therad; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor purpleColor]; self.therad = [[LFPermenantThread alloc] init]; // Do any additional setup after loading the view. } // 子线程需要执行的任务 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.therad executeTask:^{ NSLog(@"%s %@", __func__, [NSThread currentThread]); }]; } //停止这个线程 - (IBAction)stop:(id)sender { [self.therad stop]; } - (void)dealloc { NSLog(@"控制器销毁 %s",__func__); } @end
解决NSTimer在滑动时停止工作的问题
NSTimer 默认是在NSDefaultRunLoopMode 下工作的,但是当页面滑动的时候,RunLoop的当前Mode是UItrackingRunLoopMode,所以无法处理默认模式下的timer 我们可以将NSTimer 同时加入NSDefaultRunLoopMode和UItrackingRunLoopMode或者加入标记模式NSRunLoopCommonModes下 保证NSTimer在滑动和默认模式下都工作。
RunLoop的CommonMode技术
CommonMode 并不是实际存在的Mode 是一种同步source timer observer到多个mode(被标记为common的Mode)
监控应用卡顿
敬请期待
性能优化
敬请期待
RunLoop与线程
每条线城都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局字典中 线程作为key RunLoop作为Value
线程刚创建的时候并没有与之对应的RunLoop对象 RunLoop会在第一个获取他的时候创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建) 子线程默认没有开启RunLoop。
RunLoop是怎样响应用户操作的? 具体的流程是怎么样的?
由Sources1捕捉系统事件 将事件放入到事件队列里面(EventQueue) 再由Sources0处理这些事件。
RunLoop的几种状态?
1. 即将进入RunLoop 2. 即将处理Timer 3.即将处理Sources 4.即将进入休眠 5 即将从休眠中唤醒 6 即将退出RunLoop
RunLoop的Mode的作用是什么?
Mode是用来隔离的,将不同Mode的Sources0/Sources1/Timer/Observer隔离开来,互不影响。这样可以保证运行其中一种Mode的时候能比较流畅,不用处理其他Mode的Sources0/Sources1/Timer/Observer。例如:UITrackingRunLoopMode下只专心做页面滚动的事情,不会处理KCFRunLoopDefaultMode 默认模式下的事件。
RunLoop中的Mode一共分为:
KCFRunLoopDefaultMode: App的默认模式 通常主线程在这个模式下工作
UITrackingRunLoopMode: 界面跟踪Mode 用于处理ScrollView追踪触摸滑动 保证界面不受其他Mode影响
UIInitalizationRunLoopMode: 在刚启动App时刚进入的第一个Mode 启动完成后不再使用
GSEventReceiveRunLoopMode: 接受系统内部事件的内部Mode
KCFRunLoopCommonModes: 标记了(默认Mode和滚动Mode) 不是一种真正的Mode 会把Sources0/Sources1/Timer/Observer 分发到被标记的Mode中,也就是可以同时处理默认mode和滚动Mode下的事件。比如在ScrollView滑动的时候,定时器还在工作。
timer与RunLoop的关系
timer 存放在RunLoop对象中的_modes数组下面的指定的mode中的_timers数组中。如果是commonMode模式下 则timer会存在_commonModeItems里面 而且会被分配到 标记为common的模式下的mode下的timers数组里面
timer 的执行依赖于RunLoop 也就是在RunLoop的运行流程中 会先通知Observer处理timer 然后唤醒线程 执行Timer。
如何控制线程的生命周期
1.众所周知 每个线程都对应一个RunLoop 但是子线程的RunLoop是默认不开启的 也就是说子线程执行完任务后 就会销毁
2.如果我们想拥有一个我们可控制生命周期的子线程,我们可以在子线程中开启其对应的RunLoop 并为RunLoop注入Timer/Sources/Observer
3.开启RunLoop 使用[[NSRunLoop currentRunLoop] run] 这个RunLoop会停止不了 因为其底层实现是不停的调用runMode:beforeDate:方法 RunLoop不停止 线程也不会被释放,意思就是线程一直存在,如果你想要一个不会被销毁的线程可以使用这种方式,但是如果你想一个线程在需要的时候一直存在,在不需要的时候被释放掉 可以添加一个标识 当条件成立的时候 使用runMode:beforeDate:开其RunLoop 在需要停止的时候,标识取反,在使用CFRunLoopStop()方法 停止对应线程中的Loop 可确保线程销毁。