RunLoop 本质:
RunLoop 本质上是一个运行循环,其作用是保持线程的生命,防止线程被销毁,日常开发中无处不在,为了感受到 RunLoop 的存在,举个简单的例子,我们都知道,APP程序的入口在main.m里面:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
接下来我们试着感受一下 RunLoop,点击查看 UIApplicationMain,其返回值是一个 int 值:
// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no
// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.
UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nullable argv[_Nonnull], NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
我们在 main 函数里面用一个 int 变量去接收它,然后在返回值前后添加输出语句:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"1 %@", [NSThread currentThread]);
int a = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"2 %@", [NSThread currentThread]);
return a;
}
}
运行程序,看一下输出:
image.png
我们看到,只执行了第一个输出,并且当前线程是主线程,第二个输出语句并没有执行,没有执行的原因,有两种可能,第一种:UIApplicationMain 内部阻塞;第二种:UIApplicationMain 函数内部开启了一个死循环;但是一旦程序发生阻塞,APP 就无法使用了,那么说得通的可能性就是第二种,这个函数内部开启了死循环,就是 RunLoop 循环,它的作用,就是保持主线程的生命,并且帮我们监听一些事件,比如触摸、时钟、网络等;
Runloop 的两种形式:
OC: Foundation ——> NSRunLoop
// NSRunLoop获取RunLoop
[NSRunloop currentRunloop]; // 获得当前线程的RunLoop的方法
[NSRunloop mainRunloop]; // 获得主线程的RunLoop的方法
C: CoreFoudation ——> CFRunLoopRef
// CFRunLoopRef获取RunLoop
CFRunloopGetCurrent(); // 获得当前线程的RunLoop的方法
CFRunloopGetMain(); // 获得主线程的RunLoop的方法
NSRunLoop 是对 CFRunLoopRef 的进一步封装,并且 CFRunLoopRef 是线程安全的,而这一点 NSRunLoop 并不能保证。
RunLoop 与线程的关系:
每一条线程都有一个与之对应的 RunLoop 对象;
RunLoop 保存在全局的字典内,是以线程为 key,RunLoop 为 value;
RunLoop 的创建是发生在第一次获取时(懒加载, 不获取是不会创建的),RunLoop 的销毁是发生在线程结束时,只能在一个线程的内部获取其 RunLoop(主线程除外)。
RunLoop 对外的接口:
在 CoreFoundation 里面关于 RunLoop 有5个类:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef; // CFRunLoopRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef; // CFRunLoopSourceRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef; // CFRunLoopObserverRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef; // CFRunLoopTimerRef
CFRunLoopModeRef
其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装:
image.png
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source / Timer / Observer,每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个 Mode 被称作 CurrentMode,如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source / Timer / Observer,让其互不影响。
CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是 toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop 会被唤醒以执行那个回调。
CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化,RunLoop 的状态如下:
/* Run Loop Observer Activities */
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
};
上面的 Source / Timer / Observer 被统称为 mode item,一个 item 可以被同时加入多个 Mode。但一个 item 被重复加入同一个 Mode 时是不会有效果的。如果一个 Mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
RunLoop 的 Mode:
CFRunLoopMode 和 CFRunLoop 的结构大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
常见的2种 Mode:
kCFRunloopDefaultMode //程序的默认Mode
UITrackingRunloopMode //界面追踪Mode
不同 Mode 对应的事件类型都不相同,例如我们平时使用的 Timer:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
// timerWithTimeInterval:target:selector:userInfo:repeats: 创建的 Timer 需要手动添加到RunLoop中才能运行
// 两个 Mode 同时添加,在两个模式下都会运行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 默认模式下运行,但当触摸事件发生时,就会停止
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; // 有触摸事件时才会运行,但是时间结束后就会停止
// 添加到 NSRunLoopCommonModes 中,在两个模式下都会运行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)timerMethod {
NSLog(@"%@", [NSThread currentThread]);
}
通过这种方式创建的 Timer 本身是不会运行的,只有将 Timer 添加到 RunLoop 中才会运行,我们先将 Mode 设置为 NSDefaultRunLoopMode,Timer 会运行,并且打印输出, 这个时候我们往界面中添加交互控件,例如添加一个ScrollView,当我们滑动这个控件时, Timer 就会停止,响应完触摸事件又会恢复运行,这是因为 RunLoop 会优先响应 UITrackingRunloopMode 的事件,相反,如果我们将 Mode 设置为 UITrackingRunloopMode,那正常情况下 Timer 不会运行,当有 UI 事件的时候,才会运行;这和我们的初衷相背离,我们希望在任何情况下,Timer 都能运行,这个时候,我们就要将 Timer 同时添加到 NSDefaultRunLoopMod 和 UITrackingRunloopMode 两个模式下,NSRunLoopCommonModes 就是为我们解决这个问题。
注:
kCFRunloopCommonModes 默认包括了 kCFRunloopDefaultMode 和 UITrackingRunloopMode;
kCFRunloopCommonModes 并不是一个真正的模式,只是一个标记,而 kCFRunloopDefaultMode 和 UITrackingRunloopMode 的模式是真正意义的模式,timer 在设置了 kCFRunloopCommonModes 标记的模式下都能运行,而 kCFRunloopDefaultMode 和 UITrackingRunloopMode 这两个模式都是 kCFRunloopCommonModes 标记的。
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); // thread wait for receive msg
}
/// 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() 时,线程就会一直停留在这个循环里,直到超时或被手动停止,该函数才会返回。
网友评论