基本概念
在iOS和macOS中,RunLoop是一个循环机制,用于管理线程中的事件处理。当一个线程启动后,系统会为其创建一个RunLoop,它会不断地运行、检查和处理事件。如果没有事件需要处理,RunLoop会让线程进入休眠状态,从而节省系统资源。
获取当前线程的RunLoop
| 1
 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
 | 
这段代码获取了当前线程的RunLoop实例。对于主线程,这是系统自动创建的;对于后台线程,通常需要手动启动RunLoop。
RunLoop 的主要任务是:
- 处理事件和消息: 处理来自各种输入源的事件,例如触摸事件、网络事件等。
- 调度定时任务: 处理定时器触发的任务。
- 保持线程活跃: 确保线程在有任务时不会空闲而关闭。
运行模式
RunLoop可以在多种模式下运行,最常用的是kCFRunLoopDefaultMode(默认模式)和UITrackingRunLoopMode(UI跟踪模式)。RunLoop在不同模式下处理不同类型的事件。
在不同模式下添加Timer
| 12
 3
 4
 5
 
 | NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
 
 [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
 
 | 
上面代码展示了如何在RunLoop的不同模式下添加Timer。这意味着Timer在这两种模式下都会被执行。
事件源
RunLoop通过事件源(Source)来获取并处理事件。事件源主要分为输入源(Input Source)和定时源(Timer Source)。输入源主要用于处理异步事件,如用户输入、网络请求等;定时源则用于处理定时事件,如NSTimer。
自定义RunLoop输入源
| 12
 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事件的示例代码
| 12
 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);
 
 
 CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);
 
 | 
在这段代码中,myRunLoopSourcePerformRoutine函数用于处理RunLoop事件源中的事件。当有事件需要处理时,RunLoop会调用这个函数。
线程管理中的应用
在后台线程中,RunLoop通常不会自动启动。如果我们希望一个后台线程持续运行,并且能够响应事件,就需要手动启动RunLoop。
在后台线程中启动RunLoop
| 12
 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 {
 
 CFRunLoopRef runLoop = CFRunLoopGetCurrent();
 
 
 CFRunLoopTimerContext context = {0};
 
 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
 CFAbsoluteTimeGetCurrent() + 1.0,
 1.0,
 0,
 0,
 timerCallback,
 &context);
 
 
 CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
 
 
 NSLog(@"RunLoop started...");
 CFRunLoopRun();
 
 
 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 的定义和主要功能:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | typedef struct __CFRunLoop * CFRunLoopRef;
 
 
 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,并进入主循环。
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | void CFRunLoopRun(void) {
 while (!loopShouldExit) {
 
 CFRunLoopMode mode = CFRunLoopCopyCurrentMode();
 CFRunLoopPerformTasksForMode(mode);
 
 }
 }
 
 | 
- CFRunLoopPerformTasksForMode: 执行当前模式下的任务。
| 12
 3
 4
 5
 6
 7
 8
 
 | void CFRunLoopPerformTasksForMode(CFRunLoopMode mode) {
 CFRunLoopSourcePerformTasks(mode->_sources);
 
 CFRunLoopTimerPerformTasks(mode->_timers);
 
 CFRunLoopObserverPerformTasks(mode->_observers);
 }
 
 |