什么是RunLoop
- 从字面意思来看:跑圈、运动循环
- 基本用法:保持程序持续运行、处理App中的各种事件(触摸事件、定时器事件、SEL等等)
- 为什么需要它:节省CPU资源、 提高性能
如果没有RunLoop
int main (int argc, char *argv[]) {
NSLog(@"execute main function");
return 0;
}
- 如果没有RunLoop,第三行完成后程序就结束了
如果有了RunLoop
int main(int argc, char *argv[]) {
BOOL running = YES;
do {
// 执行各种任务,处理各种事件
// ...
} while (running);
return 0;
}
- 由于main函数里面启动了RunLoop,所以程序保持持续运行状态
main函数里的RunLoop
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 在UIApplicationMain函数内部就启动了一个RunLoop
- 所以UIApplicationMain函数一直没有返回,保持了程序的持续运行
- 这个默认启动的RunLoop是根主线程相关联的
RunLoop对象
- iOS中有两套API来访问和使用RunLoop
- Foundation中的NSRunloop
- Core Foundation中的CFRunLoopRef
NSRunloop和CFRunLoopRef都代表着RunLoop对象
NSRunloop是基于CFRunLoopRef封装的, 所以要了解RunLoop的内部结构,还是要研究CFRunLoopRef(Core Foundation)层面的API
RunLoop与线程
- 每条线程都有位移的一个与之对应的RunLoop对象
- 主线程的RunLoop一经自动创建好了,子线程的RunLoop需要主动创建
- RunLoop在第一次获取时创建,在线程结束时销毁
获得RunLoop对象
- Foundation
// 获得当前线程的RunLoop对象
- [NSRunLoop currentRunLoop];
// 获得主线程的RunLoop对象
- [NSRunLoop mainRunLoop];
- Core Foundation
// 获得当前线程的RunLoop对象
CFRunLoopGetCurrent();
// 获得主线程的RunLoop对象
CFRunLoopGetMain();
RunLoop的相关类
-
Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopOberverRef
-
CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop的运行模式
一个RunLoop包含若干个Mode,每个Mode有包含若干个Source/Timer/Observe
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
如果需要切换Mode,只能退出RunLoop,在重新指定一个RunLoop进入
这样做主要是为了分隔开不同组的Source/Timer/Observe,让其互不影响
-
系统默认注册了5个Mode
kCFRunLoopDefaultMode:App的默认Mode,通常主线程再是这个Mode下运行的
UITrackingRunLoopMode:界面跟踪Mode, 用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
- UIInitializionRunLoopMode:在刚启动App是进入的第一个Mode,启动时完成后就不再使用
- GSEcentRunLoopMode:接受系统时间的内部Mode,通常不会用到
- kCFRunLoopCommonMode:这是一个占位用的Mode,不是真正的Mode
-
CFRunLoopSourceRef
-
CGRunLoopRef是事件源(输入源)
以前的分类方法
Port-Based Source
Custom Input Source
Cocoa Perform Selector Source
现在的分类方法
Source0:不是基于Port的
Source1:基于Port的
-
-
CFRunLoopTimerRef
- CFRunLoopTimerRef是基于事件的触发器
- 基本上说的就是NSTimer
-
CFRunLoopObserverRef
- CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变
可以监听的时间点有以下几个
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { // 即将进入Loop kCFRunLoopEntry =(1UL << 0), // 即将处理Timer kCFRunLoopBeforeTimers =(1UL << 1), // 即将处理Source kCFRunLoopBeforeSource =(1UL << 2), // 即将进入休眠 kCFRunLoopBeforeWaiting =(1UL << 5), // 刚从休眠中唤醒 kCFRunLoopAfterWaiting =(1UL << 6), // 即将退出Loop kCFRunLoopExit =(1UL << 7), } ```
RunLoop处理逻辑 - 官方版
RunLoop的事件队列
-
每次运行RunLoop,你的线程RunLoop会自动处理之前未处理的消息,并通知相关的观察者。具体顺序如下
1.通知观察者RunLoop已经启动
2.通知观察者任何将要开始的定时器
3.通知观察者任何即将启动的非基于端口的源
4.启动任何准备好的非基于端口的源
5.如果基于端口的源准备好并处于等待状态,立即启动,并进入步骤9
6.通知观察者线程进入休眠
-
7.将线程至于休眠状态直到任一下面的事件发生:
- 某一时间到达基于端口的源
- 定时器启动
- RunLoop设置的事件已经超时
- RunLoop被显式唤醒
8.通知观察者线程将被唤醒
-
9.处理未处理的事件
- 如果用户定义的定时器启动, 处理定时器事件并重启RunLoop,进入步骤2
- 如果输入源启动,传递相应的消息
如果RunLoop被显式唤醒而且事件还没超时,重启RunLoop,进入步骤2
10.通知观察者RunLoop结束