这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
RunLoop 对外的接口
主要针对 Core Foundation中 的 RunLoop 的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:
CFRunLoopModeRef
-
CFRunLoopModeRef 代表 RunLoop 的运行模式,正是因为RunLoop 里有
Source
、Timer
、Observer
,RunLoop 才能一直保持运行,如果没有这些,RunLoop 会直接退出循环 -
一个 RunLoop 包含了若干个 Mode,每个 Mode 又包含了若干个
Source
、Timer
、Observer
-
每次 RunLoop 启动时,只能指定其中一个 Mode,这个 Mode 被称作 CurrentMode
-
如果需要切换 Mode,只能退出 RunLoop,再重新指定一个 Mode 进入循环
-
每个 Mode 可以设置自己的
Source
、Timer
、Observer
,让其互不影响 -
系统默认注册了5个 Mode(苹果只开放了前俩个)
- kCFRunLoopDefaultMode:App的默认 Mode,通常主线程是在这个 Mode 下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollerView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitalizaationRunLoopMode:在刚启动App时进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统事件内部的 Mode(绘图渲染等等),通常用不到
- kCFRunLoopCommonModes:这是占位的 Mode,不是真正的 Mode
CFRunLoopTimerRef
-
CFRunLoopTimerRef 是一个基于时间触发器(简单说就是设置一个时间,时间到了就去执行一个事件)
eg:在UI上添加一个TextView,滑动TextView,观察timer是否执行
- 方式一:
- (void)viewDidLoad { [super viewDidLoad]; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //在当前RunLoop中添加timer [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; } - (void)run{ NSLog(@"run"); }
调用
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
方法,返回定时器,系统已自动将Timer
添加到 RunLoop 中,而且默认 NSDefaultRunLoopMode 模式,这样每隔1s都会执行 run。但是当你滑动 textView 时,定时器将失效,run 方法停止执行,当你停止滑动时,run 方法继续执行。当把这个
Timer
添加到当前的 RunLoop 中时,不管你滑不滑动 textView,run 方法都会执行。- 方式二:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //1、只运行在 NSDefaultRunLoopMode 模式中 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; //2、只运行在 UITrackingRunLoopMode 模式中 [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; //3、只运行在 NSRunLoopCommonModes 模式中 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
调用
timerWithTimeInterval:target:selector:userInfo:repeats:
方法,返回定时器,这个定时器只是完成初始化,不会执行定时任务,需要手动将 timer 加入到 Runloop 中。-
只运行在
NSDefaultRunLoopMode
模式中,当你滑动 textView 时,定时器就不会执行定时任务了( run 方法不执行),停止滑动,timer 开始执行定时任务。 -
只运行在
UITrackingRunLoopMode
模式中,当你滑动textView时,定时器会执行定时任务( run 方法执行),当你不滑动textView,定时器就不会执行定时任务了( run 方法不执行) -
只运行在
NSRunLoopCommonModes
模式中,不管你滑不滑动 textView,定时器都会执行定时任务( run 方法执行)
CFRunLoopSourceRef
CFRunLoopSourceRef 代表事件的输入源。按照函数调用栈,Source 的有俩个类型
Source0
:只包含了一个回调(函数指针),它并不能主动触发事件。使用时,需要先调用CFRunLoopSourceSignal(source)
,将这个 Source 标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)
来唤醒 RunLoop,让其处理这个事件。Source1
:包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
比如一个点击事件,首先它会先包装成一个 event,之后传到 source1,source1 再分发到 source0,由 source0 处理
CFRunLoopObserverRef
CFRunLoopObserverRef 是观察者,每个 Observer
都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化
可以监听的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入Runloop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source事件
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //从刚才休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将退出runloop
kCFRunLoopAllActivities = 0x0FFFFFFFU //监听所有状态
};
如果在 RunLoop 中添加Observer
,只能用 CF 的函数,一般用于拦截系统事件等
/*
添加Observer
参数1:默认值
参数2:监听活动(这里监听了kCFRunLoopAllActivities)
参数3:重复
参数4:0
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"---监听Runloop状态改变--%lu",activity);
});
/*
添加观察者,监听Runloop的在kCFRunLoopDefaultMode模式下的状态
参数1:当前runloop
参数2:监听者
参数3:模式
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//释放Observer
CFRelease(observer);
- CoreFoundation的内存管理
- 凡是带有
Create
、copy
、retain
等字眼的函数,创建出来的对象,都需要在最后做一次release
release
函数:CFRelease(observer)
- 凡是带有
RunLoop 内部逻辑
//用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
//用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
//RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
//首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
//如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
//1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
//内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
// • 一个基于 port 的Source 的事件。
// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port);
}
//8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
//收到消息,处理消息。
handle_msg:
//9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
//9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
//9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
//执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
//source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (retVal == 0);
}
//10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。
关于 RunLoop 的内存,在 RunLoop 中,一个 RunLoop 会对应一条线程,而自动释放池是针对当前线程的一些对象,它会在kCFRunLoopBeforeWaiting
之前会被释放一次,在下一次启动的时候会重新创建