RunLoop

YVTU

基本概念

在iOS和macOS中,RunLoop是一个循环机制,用于管理线程中的事件处理。当一个线程启动后,系统会为其创建一个RunLoop,它会不断地运行、检查和处理事件。如果没有事件需要处理,RunLoop会让线程进入休眠状态,从而节省系统资源。

获取当前线程的RunLoop

1
NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 

这段代码获取了当前线程的RunLoop实例。对于主线程,这是系统自动创建的;对于后台线程,通常需要手动启动RunLoop。

RunLoop 的主要任务是:

  • 处理事件和消息: 处理来自各种输入源的事件,例如触摸事件、网络事件等。
  • 调度定时任务: 处理定时器触发的任务。
  • 保持线程活跃: 确保线程在有任务时不会空闲而关闭。

运行模式

RunLoop可以在多种模式下运行,最常用的是kCFRunLoopDefaultMode(默认模式)和UITrackingRunLoopMode(UI跟踪模式)。RunLoop在不同模式下处理不同类型的事件。

在不同模式下添加Timer

1
2
3
4
5
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
// 将Timer添加到RunLoop的默认模式中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 将Timer添加到RunLoop的UI跟踪模式中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

上面代码展示了如何在RunLoop的不同模式下添加Timer。这意味着Timer在这两种模式下都会被执行。

事件源

RunLoop通过事件源(Source)来获取并处理事件。事件源主要分为输入源(Input Source)和定时源(Timer Source)。输入源主要用于处理异步事件,如用户输入、网络请求等;定时源则用于处理定时事件,如NSTimer。

自定义RunLoop输入源

1
2
3
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &myRunLoopSourcePerformRoutine};
CFRunLoopSourceRef runLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);

在这个例子中,我们创建了一个自定义的RunLoop输入源,并将其添加到默认模式的RunLoop中。自定义的事件源允许我们处理自定义的事件。

事件处理

在RunLoop的每一次循环中,都会处理事件队列中的事件。RunLoop通过检查事件源来决定是否有需要处理的事件。如果有,它会调用对应的处理函数来处理该事件;如果没有,则进入休眠状态。

处理RunLoop事件的示例代码

1
2
3
4
5
6
7
8
9
10
11
void myRunLoopSourcePerformRoutine(void *info) {
// 处理自定义事件的逻辑
NSLog(@"RunLoop事件已处理");
}

// 自定义事件源上下文
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &myRunLoopSourcePerformRoutine};
CFRunLoopSourceRef runLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);

// 将事件源添加到RunLoop
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);

在这段代码中,myRunLoopSourcePerformRoutine函数用于处理RunLoop事件源中的事件。当有事件需要处理时,RunLoop会调用这个函数。

线程管理中的应用

在后台线程中,RunLoop通常不会自动启动。如果我们希望一个后台线程持续运行,并且能够响应事件,就需要手动启动RunLoop。

在后台线程中启动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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (void)startBackgroundTask {
NSThread *backgroundThread = [[NSThread alloc] initWithTarget:self selector:@selector(runBackgroundRunLoop) object:nil];
[backgroundThread start];
}

- (void)runBackgroundRunLoop {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
``
上面代码创建了一个后台线程,并在其中启动了RunLoop。通过添加一个NSMachPort对象,RunLoop能够保持活跃,并处理来自其他线程的事件。

## 常见使用场景

RunLoop在iOS开发中有广泛的应用场景,如处理长时间运行的任务、管理定时器和网络请求、实现复杂的UI响应逻辑等。例如,在后台下载任务中,RunLoop可以帮助保持线程的活跃,以确保下载过程不会中断。

创建一个 RunLoop 并添加一个定时器来保持线程的活动状态:
```objc
#import <Foundation/Foundation.h>

void timerCallback(CFRunLoopTimerRef timer, void *info) {
NSLog(@"Timer fired!");
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建一个新的 RunLoop
CFRunLoopRef runLoop = CFRunLoopGetCurrent(); // 通过 `CFRunLoopGetCurrent()` 获取当前线程的 `RunLoop` 实例。

// 创建一个定时器
CFRunLoopTimerContext context = {0};
// 使用 `CFRunLoopTimerCreate` 创建一个定时器,设置它在 1 秒后触发,并每秒重复触发一次。
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
CFAbsoluteTimeGetCurrent() + 1.0,
1.0,
0,
0,
timerCallback,
&context);

// 通过 `CFRunLoopAddTimer` 将定时器添加到 `RunLoop` 中,使其能够在 RunLoop 中运行。
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

// 使用 `CFRunLoopRun` 启动 `RunLoop`。这将使线程进入等待状态,直到事件触发(如定时器触发)。
NSLog(@"RunLoop started...");
CFRunLoopRun();

// 在 RunLoop 停止后移除定时器并释放资源。
CFRunLoopRemoveTimer(runLoop, timer, kCFRunLoopCommonModes);
CFRelease(timer);
}
return 0;
}

RunLoop 源码结构

RunLoop 的主要组件有输入源(Input Sources)、运行模式(RunLoop Modes)和任务队列(Tasks Queue)。

输入源包括用于处理来自 Mach 消息的事件的端口。用于处理定时器事件的定时器。RunLoop 支持不同的运行模式,每个模式可以处理不同的事件类型。例如,NSDefaultRunLoopMode 和 UITrackingRunLoopMode。任务队列存储待处理的任务,如定时器、事件等。

RunLoop 源码可以在 iOS 的底层系统库中找到。RunLoop 的实现涉及多个组件和文件。以下是一些关键部分的解析:

在 Core Foundation 框架中,CFRunLoop 是 RunLoop 的核心实现。以下是 CFRunLoop 的定义和主要功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// CFRunLoop.h
typedef struct __CFRunLoop * CFRunLoopRef;

// CFRunLoop.c
struct __CFRunLoop {
CFRuntimeBase _base;
CFMutableSetRef _modes;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableSetRef _observers;
CFMutableSetRef _timers;
// 其他成员变量
};
  • _modes: 存储 RunLoop 的运行模式。
  • _currentMode: 当前的运行模式。
  • _sources0 和 _sources1: 存储不同类型的输入源。
  • _observers: 存储观察者,处理特定的事件。
  • _timers: 存储定时器,处理定时任务。

主要函数:

  • CFRunLoopRun: 启动 RunLoop,并进入主循环。
1
2
3
4
5
6
7
8
9
void CFRunLoopRun(void) {
// 省略其他代码
while (!loopShouldExit) {
// 处理事件和任务
CFRunLoopMode mode = CFRunLoopCopyCurrentMode();
CFRunLoopPerformTasksForMode(mode);
// 检查是否需要退出
}
}
  • CFRunLoopPerformTasksForMode: 执行当前模式下的任务。
1
2
3
4
5
6
7
8
void CFRunLoopPerformTasksForMode(CFRunLoopMode mode) {
// 处理输入源
CFRunLoopSourcePerformTasks(mode->_sources);
// 处理定时器
CFRunLoopTimerPerformTasks(mode->_timers);
// 处理观察者
CFRunLoopObserverPerformTasks(mode->_observers);
}