RunLoop
顾名思义:运行循环,在程序运行过程中循环做一些事情,即用来处理事件的循环
RunLoop 应用范畴
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求(如:AFNetworking 线程保活)
- AutoreleasePool
RunLoop 基本作用
-
保持程序持续运行:程序一启动就会创建一个
主线程
,一个主线程会有与之对应的RunLoop
,RunLoop
保证主线程不会被销毁,也就保证了程序的持续运行 - 处理App中的各种事件(比如:触摸事件、定时器事件、Selector事件等)
-
节省 CPU 资源,提高程序性能:程序运行起来时,当什么操作都没有做的时候,RunLoop就会通知 CPU,现在没有事情做,我要去休息,这时CPU 就会将其资源释放出来去做其他的事情,当有事情做的时候RunLoop就会被唤醒去做事情。(
没有消息就让线程休眠,有消息就唤醒线程
)
RunLoop 对象
iOS 中有2套API来访问和使用 RunLoop:
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
NSRunLoop
和 CFRunLoopRef
都代表着 RunLoop对象
- NSRunLoop 是基于 CFRunLoopRef 的一层OC包装
- CFRunLoopRef 是开源的:https://opensource.apple.com/tarballs/CF/
RunLoop 与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
-
RunLoop
保存在一个全局的Dictionary里,线程作为key,RunLoop作为value - 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
获取 RunLoop 对象
- Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
- Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
RunLoop 的构成
RunLoop-
Core Foundation
中关于RunLoop
的5个类CFRunLoopRef // runloop对象 CFRunLoopModeRef // 运行模式 CFRunLoopSourceRef CFRunLoopTimerRef CFRunLoopObserverRef
结构体如下:
typedef struct __CFRunLoop * CFRunLoopRef;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoop {
pthread_t _pthread; // 线程
CFMutableSetRef _commonModes; // commonModes下的两个mode(kCFRunloopDefaultMode和UITrackingMode)
CFMutableSetRef _commonModeItems; // 在commonModes状态下运行的对象(例如Timer)
CFRunLoopModeRef _currentMode; // 运行的所有模式(CFRunloopModeRef类)
CFMutableSetRef _modes; // 在当前 loop 下运行的mode
...
};
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
...
};
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
会立马退出
目前已知的 5 种 Mode:
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode:在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
-
kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode,包含
kCFRunLoopDefaultMode
和UITrackingRunLoopMode
CommonModes
一个 Mode 可以将自己标记为Common
属性(通过将其 ModeName
添加到 RunLoop
的 commonModes
中)。每当 RunLoop
的内容发生变化时,RunLoop
都会自动将 _commonModeItems
里的 Source/Observer/Timer
同步到具有 Common
标记的所有 Mode 里。
_sources0 和 _sources1
-
Sources0: 触摸事件处理,
performSelector: onThread:
,非基于Port的 - Sources1: 基于Port的线程间通信,系统事件捕捉
_timers
定时执行的定时器,底层基于使用 mk_timer
实现,受 RunLoop
的 Mode 影响(GCD的定时器不受 RunLoop
的Mode 影响);当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。如果线程阻塞或者不在这个Mode下,触发点将不会执行,一直等到下一个周期时间点触发。
timer 和 sources1(也就是基于 port 的 sources)可以反复使用,比如 timer 设置为 repeat,port 可以持续接收消息,而 sources0 在一次触发后就会被 runloop 移除。
_observers
- 用于监听 RunLoop 的状态
- UI刷新(BeforeWaiting)
- AutoreleasePool(BeforeWaiting)
监听方法
// 创建Observer
CFRunLoopObserverRef observer = 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;
}
});
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
监听状态
/* Run Loop Observer Activities */
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
kCFRunLoopAllActivities = 0x0FFFFFFFU // 包含上面所有状态
};
RunLoop 运行逻辑
RunLoop 运行逻辑查看源码
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode);
网友评论