简介
- 顾名思义
- 运行循环
- 在程序运行过程中循环做一些事情
- 应用范畴
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
加入没有RunLoop
- 执行完第13行代码后,会即将退出程序
有了RunLoop
- UIApplicationMain 创建了一个runLoop对象
- 假如我们删除了这段代码 点击开启APP会立刻闪退。
- 伪代码猜想这个 RunLoop 的原理
- retVal一直为0 程序才不会退出
- 让线程睡觉并等待
- 当程序发生事件就会唤醒并处理这个事件
- 处理完成后返回0 继续循环程序一直不会退出
- 程序并不会马上退出,而是保持运行状态
- RunLoop的基本作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- ......
RunLoop对象
- iOS中有2套API来访问和使用RunLoop
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
- NSRunLoop和CFRunLoopRef都代表着RunLoop对象
- NSRunLoop是基于CFRunLoopRef的一层OC包装
- CFRunLoopRef是开源的
- opensource.apple.com/tarballs/CF… 下载
RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
- UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 这个我们可以理解为一开始创建主线程的时候是没有runloop的,但是他同时调用了
[NSRunLoop currentRunLoop]
主动获取(创建)了主线程的runloop。
- UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 这个我们可以理解为一开始创建主线程的时候是没有runloop的,但是他同时调用了
获取RunLoop对象
-
Foundation
- [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
- [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
-
Core Foundation
- CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
- CFRunLoopGetMain(); // 获得主线程的RunLoop对象
-
源码探究
- 1.runloop 从字典中根据线程作为key返回
- 2.加入loop为空 就创建一个newloop 赋值给这个loop存入字典中
RunLoop相关的类
- Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
- 简化有用的部分
- _pthread : 线程 所以说一个线程一个runloop
- CFMutableSetRef : 集合 相当于数组 但是数组是有序的,集合是无序的
- _modes : 里面装有的数据是CFRunLoopModeRef
- _currentMode : 当前模式
- runloop 中有许多的mode放在modes里但是只有一个mode被认为是currentMode,猜测是从中取出一个mode已经响应后再取出别的响应。
- _name : 模式的名称
- 4个集合
- _sources0 和 _source1 是 CFRunLoopSourceRef的集合对象
- _observers 是 CFRunLoopObserverRef的集合对象
- _timers 是 CFRunLoopTimerRef的集合对象
CFRunLoopModeRef
-
CFRunLoopModeRef代表RunLoop的运行模式
-
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
-
RunLoop启动时只能选择其中一个Mode,作为currentMode
-
如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入 不会退出程序。
- 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
-
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
-
常见的2种Mode
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
- runloop的默认模式
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- runloop的滑动模式
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
CFRunLoopObserverRef
添加Observer监听RunLoop的所有状态
RunLoop的运行逻辑
- Source0
- 触摸事件处理
- performSelector:onThread:
- Source1
- 基于Port的线程间通信
- 系统事件捕捉
- Timers
- NSTimer
- performSelector:withObject:afterDelay:
- Observers
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- Autorelease pool(BeforeWaiting)
- 01、通知Observers:进入Loop
- 02、通知Observers:即将处理Timers
- 03、通知Observers:即将处理Sources
- 04、处理Blocks
- 05、处理Source0(可能会再次处理Blocks)
- 06、如果存在Source1,就跳转到第8步
- 07、通知Observers:开始休眠(等待消息唤醒)
- 08、通知Observers:结束休眠(被某个消息唤醒)
- 01> 处理Timer
- 02> 处理GCD Async To Main Queue
- 03> 处理Source1
- 09、处理Blocks
- 10、根据前面的执行结果,决定如何操作
- 01> 回到第02步
- 02> 退出Loop
- 11、通知Observers:退出Loop
我的类比理解 这是我的理解可以不作为参考。
- 通俗的来讲就是说runloop相当于一台机器,机器有各种模式。
- 各个模式里面都有4个数组,类比4个旋钮吧。4个旋钮调节是不会互相影响的,可以调节旋钮进行自定义。
- 当你要开启机器时要先选择一种模式,作为当前模式运行机器,想要切换模式要先停止当前模式才能切换。
- 如果你选择的模式4个旋钮值都是0,机器自动关闭,进入随眠状态等待事件的激活。
- 第4个旋钮不仅能控制机器执行某些动作,还能监听机器运行的状态
- runloop中循环做的事情
- 进入某种模式,把模式先的source0、source1、timers、observer取出来执行
源码分析
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
//通知observers : 进入loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知obsercers:退出Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
/* 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();
int32_t retVal = 0;
do {
//通知obsercers:即将处理timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知obsercers:即将处理sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//通知obsercers:即将处理block
__CFRunLoopDoBlocks(rl, rlm);
//处理sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
//处理block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//判断有无source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//如果有source1,跳转到handle_msg
goto handle_msg;
}
}
//通知Observers:即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//开始休眠
__CFRunLoopSetSleeping(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
//等待别的消息来唤醒当前线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
//唤醒
__CFRunLoopUnsetSleeping(rl);
//通知observer:结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
if (被timer唤醒) {
//处理timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()))
}
else if (被GCD唤醒) {
//处理GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
//source1唤醒
//处理source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
//处理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;
}
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
} while (0 == retVal);
return retVal;
}
runLoop执行流程
RunLoop休眠的实现原理
-
等待别的消息来唤醒当前线程 开始睡觉 :线程堵塞不会往下走了
-
不同于 while(1)这样的线程阻塞,但是这样的线程并没有真正的休息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
-
这个就是内核层面的API 让线程休眠,传递消息到内核层面CPU休息,达到省电的功能。
-
我们只能控制的是页面层面的API
RunLoop在实际开中的应用
- 控制线程生命周期(线程保活)
- 解决NSTimer在滑动时停止工作的问题
- 监控应用卡顿
- 性能优化
应用1 解决NSTimer在滑动时停止工作的问题
- 1.创建一个计时器
- 放置一个textView
- 运行后发现当我们拖动textView的时候计时器是停止计时的
- 原理分析
- runloop 同一时间只能运行一种模式。
- 定时器不在拖拽模式下,是在默认模式中工作的。
- 如果定时器在拖拽模式下就可以运行了。
- 将计时器放入拖拽模式中也在默认模式中 就放到NSRunLoopCommonModes
- scheduledTimerWithTimeInterval 这个方法默认将计时器放到默认模式中
- (void)viewDidLoad {
[super viewDidLoad];
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
// NSLog(@"%d", ++count);
// }];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end