转载自李峰峰博客:https://juejin.im/entry/587c2c4ab123db005df459a1
Runloop官方文档
参考:https://www.cnblogs.com/kenshincui/p/6823841.html#
1、Runloop与线程间的关系
- 每条线程都有唯一的一个与之对应的RunLoop对象。
- 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建。
- RunLoop在第一次获取时创建,在线程结束时销毁。
2、主线程相关联的RunLoop创建
CFRunLoopRef源码:
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程 根据传入的主线程创建主线程对应的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop)
3、子线程相关联的RunLoop创建
苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:
- 获得主线程的RunLoop对象:CFRunLoopGetMain()
- 获得当前线程的RunLoop对象:CFRunLoopGetCurrent()
CFRunLoopRef源码:
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
[NSRunLoop currentRunLoop];方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。
201901更新
runloop是为线程而生,没有线程runloop就没有存在的必要!
1、什么是runloop?
runloop是事件接收和分发机制的一个对象,处理运行中出现的各种事件(如:选择器事件、计时器事件、UI刷新事件、触摸事件),保证程序的持续运行。一个线程只能执行一个任务,执行完就休眠(节省CPU资源,提高性能),等待下次唤醒继续工作。
2、基本作用
2.1、保持程序运行
程序一启动,默认创建并开启主线程,主线程不会被销毁,保证持续运行,从而让程序持续运行。
2.2、处理各类事件
选择器事件、计时器事件、UI刷新事件、触摸事件
2.3、节省CPU、提高性能
当程序启动,没有事件处理,runloop会通知cpu现在无需工作,要去休眠,此时,cpu就会把资源释放出来去处理其他事情。当接收到事件开启线程处理,runloop会立即唤醒处理事件。
右边 【输入源】将异步事件传递给相应的处理程序,并调用runUntilDate:方法。【计时器源】交给左边线程的runloop处理,左边runloop会把右边输入源传过来的消息交给runloop对应的方法去处理,处理完成结束。
3、runloop开启
3.1、主线程runloop开启
程序入口main函数开启主线程的runloop,一点开启,只有但程序关闭才会销毁主线程的runloop,这也证明了runloop保证了程序的持续运行。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
runloop源码
// 用DefaultMode启动
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
源码表明,runloop是do while判断result的值来实现的,如果没有开启主线程runloop 则程序将直接返回,结束运行。
4、runloop与线程
4.1、runloop与线程的关系
每一条线程都有唯一对应的runloop对象。
主线程的runloop在程序启动的时候默认开启,子线程的runloop需要手动创建并开启。
runloop在第一次获取的时候创建,在线程结束的时候销毁/休眠。
4.2、runloop的创建
主线程创建源码
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程 根据传入的主线程创建主线程对应的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
子线程创建源码
不允许直接创建runloop,只能通过API获取
获取主线程runloop:CFRunLoopGetMain()
获取当前线程runloop:CFRunLoopGetCurrent()
线程和runloop是一一对应的,源码说明 【线程为key, runloop为value】的形式存储在一个【全局字典】当中。
流程:当使用[NSRunLoop currentRunLoop];获取runloop的时候,如果值(runloop)为空,则初始化全局字典并创建一个runloop,存储到全局字典中。如果有则直接从字典中取,当取不到的时候则创建一个,并注册一个回调,当线程处理事件后销毁,把对应的runloo也销毁/休眠。
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
5、runloop相关类
CFRunLoopRef //获得当前RunLoop和主RunLoop
CFRunLoopModeRef //运行模式,只能选择一种,在不同模式中做不同的操作
CFRunLoopSourceRef //事件源,输入源
CFRunLoopTimerRef //定时器时间
CFRunLoopObserverRef //观察者
5.1、CFRunLoopModeRef 运行Model
系统默认注册了5个Model
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:
在runloop中包含了若干个Model,每个 Model又包含了若干个 Source/Timer/Observer。每次调用runloop主函数时,只能指定一个model,这个model称为CurrentMode(当前线程model)。如果需要切换model,需先退出当前的runloop,再指定新的model。这样的目的是分隔不同组的model,互不影响。
mode中的Source/Timer/Observer 被统称为model item,一个item可以被同时加入到多个model中,但一个item被重复添加到同一个model是不会有效果。如果model中一个item都没有,则runloop会直接退出,不会进入循环。
Model间的切换
使用NSTimer每一段时间执行一些事情时滑动UIScrollView,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况。
解决方案:使用占位的运行Model NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的在默认model及滑动Model中都可以运行。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 加入到RunLoop中才可以运行
// 1. 把定时器添加到RunLoop中,并且选择默认运行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 当textFiled滑动的时候,timer失效,停止滑动时,timer恢复
// 原因:当textFiled滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,因此timer失效,当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,因此timer又会重新启动了
// 2. 当我们将timer添加到UITrackingRunLoopMode模式中,此时只有我们在滑动textField时timer才会运行
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 3. 那个如何让timer在两个模式下都可以运行呢?
// 3.1 在两个模式下都添加timer 是可以的,但是timer添加了两次,并不是同一个timer
// 3.2 使用站位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的都可以运行,下面两种模式被打上标签
//0 : <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"}
//2 : <CFString 0x10a8e85e0 [0x10a8c7a40]>{contents = "kCFRunLoopDefaultMode"}
// 因此也就是说如果我们使用NSRunLoopCommonModes,timer可以在UITrackingRunLoopMode,kCFRunLoopDefaultMode两种模式下运行
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(@"%@",[NSRunLoop mainRunLoop]);
}
-(void)show
{
NSLog(@"-------");
}
5.2、CFRunLoopSourceRef事件源,输入源
Source分为两种:
Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕)
Source1:基于Port的 通过内核和其他线程相互发送消息(与内核相关)
注意:Source1在处理的时候会分发一些操作给Source0去处理
【事件响应和手势识别底层处理是一致的吗,为什么?】
事件响应:
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
手势识别:
当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。
苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
简而言之就是处理事件流程、任务分发、系统回调不一样