RunLoop详解

什么是RunLoop

顾名思义,RunLoop就是运行循环,在程序运行过程中循环做一些事情。在iOS系统中RunLoop有以下应用场景:

  1. 定时器、PerformSelector
  2. CGD Async Main Queue
  3. 事件响应、手势识别、界面刷新
  4. 网络请求
  5. AutoreleasePool

试想以下如果程序没有RunLoop会怎样?

1
2
3
4
5
6
int main(int argc, const char * argv[]){
@autoreleasepool{
NSLog(@"Hello, World!");
}
return 0;
}

执行完打印语句该程序就会退出运行。

那么如果有了RunLoop呢?我们看下面的代码

1
2
3
4
5
int main(int argc, char *argv[]){
@autoreleasepool {
return UIApplicationMain(argc,argv,nil,NSStringFromClass([Appdelegate class]));
}
}

这是一个APP程序的main函数,这个函数启动后APP是不会自动退出的,这是为什么呢?原因就是 UIApplicationMain 函数里面有运行循环,大致的原理跟下面的代码类似:

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char *argv[]){
@autoreleasepool {
int retVal = 0;
do {
// 睡眠中等待消息
int message = sleep_and_wait();
// 处理消息
retVal = process_message(message);
}while( 0 == retVal);
return 0;
}
}

那么通过上面的分析我们发现RunLoop的基本作用应该是:

  1. 保持程序的持续运行
  2. 处理App中的各种事件(比如触摸事件、定时器事件等)
  3. 节省CPU资源,提高程序性能:该做事的时候做事,该休息的时候休息
  4. ……

RunLoop对象

iOS中有两套API来访问和使用RunLoop

  1. Foundation: NSRunLoop
  2. Core Foundation: CFRunLoopRef

NSRunLoopCFRunLoopRef都代表着RunLoop对象

  1. NSRunLoop是基于CFRunLoopRef的一层OC包装
  2. CFRunLoopRef是开源的:https://opensource.apple.com/tarballs/CF/

image-20210829211800436

RunLoop与线程

  1. 每条线程都有唯一的一个与之对应的RunLoop对象
  2. RunLoop保存在一个全局的Dictionary里,线程为Key,RunLoop作为value
  3. 线程刚创建的时候没有RunLoop对象,RunLoop会在第一次获取它时创建
  4. RunLoop会在线程结束是销毁
  5. 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

获取RunLoop对象

1
2
3
4
5
6
7
Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 或得主线程的RunLoop对象

Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop相关的类

CoreFoundation中关于RunLoop的5个类

  1. CFRunLoopRef
  2. CFRunLoopModeRef
  3. CFRunLoopSourceRef
  4. CFRunLoopTimerRef
  5. CFRunLoopObserverRef
1
2
3
4
5
6
7
8
typedef struct __CFRunLoop *CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
1
2
3
4
5
6
7
8
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
}

image-20210829224805570

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会立马退出

常见的两种Mode:

  1. kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程在这个Mode下运行
  2. UITrackingRunLoopMode: 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响

CFRunLoopObserverRef

1
2
3
4
5
6
7
8
9
typedef CF_OPTIONS(CFOptionFlagsCFRunLoopActivity){
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
}

添加Observer监听RunLoop的所有状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,^void(CFRunLoopObserverRef observer,CFRunLoopActivity activity){
switch(activity){
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);

RunLoop的运行逻辑

image-20210829233827287

Source0

  1. 触摸事件处理
  2. performSelector:onThread:

Source1

  1. 基于Port的线程间通信
  2. 系统事件捕捉

Timer

  1. NSTimer
  2. performSelector:withObject:afterDelay:

Observers

  1. 用于监听RunLoop的状态
  2. UI刷新(BeforeWaiting)
  3. AutoReleasePool(BeforeWaiting)

运行流程介绍:

image-20210829234950660

RunLoop休眠的实现原理

image-20210829235246778

RunLoop在实际开发中的应用场景

  1. 控制线程的声明周期(线程保活)
  2. 解决NSTimer在滑动时停止工作的问题
  3. 监控应用卡顿
  4. 性能优化
谢谢您的支持!