RunLoop 本质:安全
RunLoop 本质上是一个运行循环,其做用是保持线程的生命,防止线程被销毁,平常开发中无处不在,为了感觉到 RunLoop 的存在,举个简单的例子,咱们都知道,APP程序的入口在main.m里面:bash
#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;
}
}
复制代码
运行程序,看一下输出: oop
咱们看到,只执行了第一个输出,而且当前线程是主线程,第二个输出语句并无执行,没有执行的缘由,有两种可能,第一种:UIApplicationMain 内部阻塞;第二种:UIApplicationMain 函数内部开启了一个死循环;可是一旦程序发生阻塞,APP 就没法使用了,那么说得通的可能性就是第二种,这个函数内部开启了死循环,就是 RunLoop 循环,它的做用,就是保持主线程的生命,而且帮咱们监听一些事件,好比触摸、时钟、网络等;ui
Runloop 的两种形式:spa
OC: Foundation ——> NSRunLoop线程
// NSRunLoop获取RunLoop
[NSRunloop currentRunloop]; // 得到当前线程的RunLoop的方法
[NSRunloop mainRunloop]; // 得到主线程的RunLoop的方法
复制代码
C: CoreFoudation ——> CFRunLoopRef指针
// CFRunLoopRef获取RunLoop
CFRunloopGetCurrent(); // 得到当前线程的RunLoop的方法
CFRunloopGetMain(); // 得到主线程的RunLoop的方法
复制代码
NSRunLoop 是对 CFRunLoopRef 的进一步封装,而且 CFRunLoopRef 是线程安全的,而这一点 NSRunLoop 并不能保证。code
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 的接口进行了封装:
一个 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() 时,线程就会一直停留在这个循环里,直到超时或被手动中止,该函数才会返回。