小记一下,没有整理
RunLoop简介
- RunLoop:顾名思义,运行循环,在程序运行过程中循环做一些事情
- 应用范畴:定时器,GCD,事件响应,手势识别,界面刷新,网络请求,AutoreleasePool
- 如果没有RunLoop,程序执行完之后会直接退出
- 如果有了RunLoop,程序并不会马上退出,而是保持运行状态
- RunLoop的基本作用:保持程序的持续运行,处理APP中各种事件(触摸事件,定时器事件),节省CPU资源,提高程序性能。
- 伪代码(main函数里RunLoop的循环伪代码)

RunLoop也是一个对象(NSRunLoop,CFRunLoopRef(NSRunLoop是基于CFRunLoopRef(C语言)的一层OC包装) 访问RunLoop对象  
而且它们都调用了_CFRunLoopGet0这个函数
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__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));
__CFSpinUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__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对象 RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取线程的RunLoop时创建,RunLoop会在线程结束时销毁 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
RunLoop与线程的关系

RunLoop相关的类
(关于这五个类的具体描述去看这里吧! Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef


通过 mode name 来操作内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。 
这张图很好,在RunLoop里面有很多Mode,正在用的那个叫currentMode(在这些Mode中的且只有一个) 对里面的source和observe进行处理 这里有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
同时苹果还提供了一个操作 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用这个字符串来操作 Common Items,或标记一个 Mode 为 “Common”。使用时注意区分这个字符串和其他 mode name。
注意: NSTimer在添加到runloop中的时候要用NSRunLoopCommonModes作为参数。 理由:并且NSTimer创建好之后默认是加入名为kCFRunLoopDefaultMode的model中,所以只有应用程序在kCFRunLoopDefaultMode下,Timer才会工作。如果这个时候滑动了屏幕,那么应用的mode就从名字为kCFRunLoopDefaultMode的mode中退出,进入到名称为UITrackingRunLoopMode的model中。因为NSTimer没有添加到名字为UITrackingRunLoopMode的item中,所以只有等到不再滑动,即回到kCFRunLoopDefaultMode的时候才再次开始工作。通过NSRunLoopCommonModes标记之后,Runloop会把NSTimer加入到Runloop中的commonModeItems中。上面讲过RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Model里。

CFRunLoopModeRef
可以保证模式之间是相互隔离的,互不影响。使运行更加流畅。 
两种常见的Mode
苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode。
系统默认注册了5个Mode:
- kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
- UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
- kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
RunLoop的运行逻辑
就是处理Mode中的四个(timers,source1,source2,Observers) 
RunLoop的几种状态

添加Observer监听RunLoop的所有状态




RunLoop的内部实现运行逻辑
内部循环睡觉唤醒又睡觉 UI界面的刷新,定时器的处理,点击事件最终都是由RunLoop控制处理的。 RunLoop入口源码 通过打断点。我们猜测RunLoop最先进来的是CFRunLoopRunSpecific这个函数 

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(rl, rlm);
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
__CFRunLoopUnsetSleeping(rl);
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被timer唤醒) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
}else if (被GCD唤醒) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
__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);
return retVal;
}
调用细节
在这些过程中,有一个调用细节,就是这些过程中通常都会调用一个__CFRUNLOOP_IS_CALLING_OUT_TO 开头的函数来处理Observer事件(UI之类的)
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
验证: 
GCD的很多流程是不依赖于RunLoop的,不过GCD有一种情况回交给RunLoop去处理 也是调用一个很长的函数来交给RunLoop

总结: 
关于休眠的细节
等待唤醒的时候,是真正的休眠,不会占用CPU内存 但是如果是普通的循环阻塞,就会占用内存空间。(没有切换到内核态,仍然在用户态)
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
在等待唤醒的这个__CFRunLoopServiceMachPort 函数中,有调用一个mach_msg,达到休眠的目的 内核层面API(mach_msg) / 应用层面API(搭网络请求) 
RunLoop响应用户操作的具体流程
- Sounce1系统事件捕捉放到队列中
- 下一步由Source0处理
RunLoop在实际开发中的应用
- 线程保活
- 解决NSTimer在滑动时停止工作的问题
- 监控应用卡顿
- 性能卡顿
利用RunLoop解决NSTimer问题
 创建一个NSTimer,是NSRunLoopCommonModes的Mode模式 在NSRunLoopCommonModes模式是存储在commonModesItems中,它里面包含有Default和Track两种状态。这样即便在另一个线程中,timer也不会停。 timer本来是在modes里面的timer里面。当它是NSRunLoopCommonModes模式时,就存储在commonModesItems中了。 在RunLoop运行过程中,是会通知Observer处理Timer的。
线程保活
比如AFNetworking 默认情况在子线程执行任务,执行结束后就会dealloc  RunLoop成功阻塞了子线程,一直没有打印end,处于了休眠状态
再举一个例子,我们看线程保活后再调用子线程执行任务的话,是不是仍然是之前RunLoop阻塞的线程被唤醒了。是的!
- (void)test {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)run {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s end -----", __func__);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
NSLog(@"123");
}
2021-08-12 08:45:05.043421+0800 RunLoop[27559:1728622] -[ViewController run] <MHThread: 0x600001dc22c0>{number = 6, name = (null)}
2021-08-12 08:45:07.630941+0800 RunLoop[27559:1728548] 123
2021-08-12 08:45:07.698299+0800 RunLoop[27559:1728622] -[ViewController test] <MHThread: 0x600001dc22c0>{number = 6, name = (null)}
2021-08-12 08:45:08.830087+0800 RunLoop[27559:1728548] 123
2021-08-12 08:45:08.830753+0800 RunLoop[27559:1728622] -[ViewController test] <MHThread: 0x600001dc22c0>{number = 6, name = (null)}
可以看到点击后调用的子线程就是RunLoop之前阻塞现在休眠了的子线程,它现在被唤醒了。
|