iOS Runtime 详细介绍
- 一、Runtime 概述
- Runtime(运行时)是 Objective-C 的核心底层机制,它负责将代码的编译时逻辑转换为运行时动态行为。
- Objective-C 作为一门动态语言,其特性(如动态类型、动态方法调用)均依赖 Runtime 实现。
- Runtime 本质是一套 C 语言 API(位于 <objc/runtime.h>),提供了操作类、对象、方法等的底层接口。
- 二、Runtime 核心组成
- 对象(Object)与类(Class)
- 对象:Objective-C 中所有对象本质是 objc_object 结构体,包含一个指向类的指针 isa(即“对象的类型”)。
struct objc_object {
Class isa; // 指向对象所属的类
};
- 类:类本身也是对象(称为“元类”,Meta Class),Class 结构体包含类的方法列表、属性列表、协议列表等。
struct objc_class {
Class isa; // 指向元类
Class super_class; // 父类
const char *name; // 类名
// 方法列表、属性列表、协议列表等...
};
- 方法(Method) 方法由 objc_method 结构体表示,包含方法名(SEL)、实现(IMP,函数指针)、参数类型(types):
struct objc_method {
SEL method_name; // 方法选择器(如 @selector(doSomething:))
char *method_types; // 参数/返回值类型编码(如 "v@:" 表示无返回值、id self、SEL _cmd)
IMP method_imp; // 方法实现的函数指针
};
- SEL 与 IMP
- SEL(方法选择器):字符串唯一标识方法名(如 @selector(viewDidLoad)),编译时生成,相同方法名的 SEL 全局唯一。
- IMP(方法实现):指向方法具体代码的函数指针,通过 SEL 可找到对应的 IMP(即“消息发送”过程)。
- 三、Runtime 动态特性(核心机制)
- 消息发送(Message Sending) Objective-C 方法调用本质是“发送消息”,语法 [obj doSomething] 会被编译器转换为 Runtime 函数 objc_msgSend(obj, @selector(doSomething))。
- 流程:
- 通过对象的 isa 指针找到类,在类的方法列表中查找 SEL 对应的 IMP;
- 若未找到,递归查找父类;
- 若最终未找到,触发“动态方法解析”或“消息转发”。
- 动态方法解析(Dynamic Method Resolution) 当对象收到无法响应的消息时,Runtime 会先调用类的“动态方法解析”方法,允许类在运行时动态添加方法实现:
实例方法:+ (BOOL)resolveInstanceMethod:(SEL)sel
类方法:+ (BOOL)resolveClassMethod:(SEL)sel 示例:动态为对象添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethod)) {
// 添加方法实现:参数为 (id self, SEL _cmd),无返回值
class_addMethod([self class], sel, (IMP)dynamicMethodImpl, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodImpl(id self, SEL _cmd) {
NSLog(@"动态添加的方法被调用");
}
- 消息转发(Message Forwarding) 若动态方法解析失败,Runtime 会进入“消息转发”流程,允许将消息转发给其他对象处理:
快速转发:通过 - (id)forwardingTargetForSelector:(SEL)aSelector 返回一个能处理该消息的对象(效率高,推荐优先使用)。
标准转发:若快速转发未处理,会调用 methodSignatureForSelector: 获取方法签名,再通过 forwardInvocation: 将消息转发给其他对象(可修改参数、返回值)。
- 方法混淆(Method Swizzling) 通过 Runtime 交换两个方法的 IMP,实现“A 方法调用时执行 B 逻辑,B 方法调用时执行 A 逻辑”。常用于无侵入式埋点、崩溃防护等。
- 示例:交换 ViewController 的 viewDidAppear: 方法
#import <objc/runtime.h>
@implementation ViewController (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSEL = @selector(viewDidAppear:);
SEL swizzledSEL = @selector(swizzled_viewDidAppear:);
Method originalMethod = class_getInstanceMethod(self, originalSEL);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSEL);
// 交换 IMP
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)swizzled_viewDidAppear:(BOOL)animated {
// 执行原方法逻辑(此时 swizzled_viewDidAppear 已与 viewDidAppear 交换,调用自身即调用原方法)
[self swizzled_viewDidAppear:animated];
// 添加自定义逻辑(如埋点)
NSLog(@"页面出现:%@", self.class);
}
@end
- 四、Runtime 常见应用场景
- 关联对象(Associated Objects):为分类(Category)添加属性(分类默认不能添加成员变量,通过 objc_setAssociatedObject 实现)。
- JSON 模型转换:通过 Runtime 获取类的属性列表,自动将 JSON 字典映射为模型对象(如 YYModel、MJExtension 底层实现)。
- 崩溃防护:拦截未实现的方法(如 NSArray 越界、unrecognized selector),通过消息转发返回默认值避免崩溃。
iOS Runloop 详细介绍
一、Runloop 概述
- Runloop(运行循环)是 iOS 中管理线程事件处理的核心机制,本质是一个 “事件循环”:不断从“事件源”接收事件、处理事件,直到线程结束。
- 其核心作用是 “让线程在需要时工作,不需要时休眠”,避免线程空转浪费资源。
二、Runloop 核心作用
- 保持线程存活:主线程默认启动 Runloop,因此能一直运行不退出;子线程默认无 Runloop,执行完任务后会销毁。
- 事件处理:统一处理输入源事件(如触摸、网络回调、定时器)、UI 刷新事件等。
- 线程休眠与唤醒:无事件时让线程进入休眠状态(降低 CPU 占用),有事件时唤醒线程处理。
三、Runloop 核心组成(基于 Core Foundation)
-
Runloop 相关接口位于 <CoreFoundation/CFRunLoop.h>,核心结构体为 CFRunLoopRef,其组成如下:
-
Runloop Mode
-
运行模式(如 kCFRunLoopDefaultMode、UITrackingRunLoopMode),同一时间 Runloop 只能处于一种 Mode。}
-
Source
-
事件源(输入源):分为 Port-Based Sources(如 NSMachPort)和 Custom Sources(自定义事件)。
-
Timer
-
定时器源:基于时间触发的事件(如 NSTimer,需添加到 Runloop 才会生效)。
-
Observer
-
观察者:监听 Runloop 状态变化(如进入、退出、处理事件前/后)。
四、Runloop 工作原理(生命周期)
- Runloop 按固定流程循环运行,核心步骤如下:
- 通知 Observer:进入 Runloop(kCFRunLoopEntry)。
- 通知 Observer:即将处理 Timers(kCFRunLoopBeforeTimers)。
- 通知 Observer:即将处理 Sources(kCFRunLoopBeforeSources)。
- 处理所有就绪的 Sources(同步执行 Source 回调)。
- 若有 Source1 未处理,跳回步骤 2(Source1 是基于端口的异步事件,可能触发新事件)。
- 通知 Observer:即将进入休眠(kCFRunLoopBeforeWaiting),线程进入休眠(释放 CPU)。
- 休眠被唤醒(由 Source 事件、Timer 触发或外部唤醒),通知 Observer:结束休眠(kCFRunLoopAfterWaiting)。
-
- 若唤醒源是 Timer:执行 Timer 回调,跳回步骤 2。
-
- 若唤醒源是 Source:执行 Source 回调,跳回步骤 2。
-
- 若唤醒源是 Runloop 停止:进入步骤 8。
- 通知 Observer:退出 Runloop(kCFRunLoopExit),Runloop 终止。
五、Runloop 与线程的关系
- 一一对应:每个线程(包括主线程、子线程)都有唯一对应的 Runloop(通过 CFRunLoopGetCurrent() 获取),但子线程的 Runloop 默认未创建,需手动调用 CFRunLoopRun() 启动。
- 主线程 Runloop:系统自动创建并启动(UIApplicationMain 函数中触发),Mode 默认为 kCFRunLoopDefaultMode(App 正常运行模式)和 UITrackingRunLoopMode(滑动模式,如 UIScrollView 滚动时切换到此 Mode,避免非滑动事件干扰)。
六、Runloop 常见应用场景
- NSTimer 与 Runloop
- NSTimer 需添加到 Runloop 才会生效,且受 Mode 影响。例如:
// 添加到默认 Mode:滑动时 Timer 会暂停(因滑动时 Runloop 切换到 UITrackingRunLoopMode)
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerFire) userInfo:nil repeats:YES];
// 添加到 Common Modes(包含 Default + Tracking 等 Mode):滑动时 Timer 不暂停
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerFire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
子线程保活 子线程默认执行完任务后销毁,通过启动 Runloop 可让其长期存活(用于处理异步回调):
// 子线程保活示例
- (void)keepThreadAlive {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil];
[thread start];
}
- (void)threadMain {
@autoreleasepool {
// 创建并启动 Runloop(需添加 Source/Observer 避免立即退出)
CFRunLoopSourceContext context = {0};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
// 启动 Runloop(永久循环,需手动调用 CFRunLoopStop 退出)
CFRunLoopRun();
}
}
性能优化 通过 Observer 监听 Runloop 状态,可检测卡顿(如处理事件耗时超过阈值):
// 监听 Runloop 处理事件耗时(简化示例)
- (void)monitorRunloop {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
static CFAbsoluteTime lastActivityTime;
if (activity == kCFRunLoopBeforeSources) {
lastActivityTime = CFAbsoluteTimeGetCurrent();
} else if (activity == kCFRunLoopAfterWaiting) {
CFAbsoluteTime cost = CFAbsoluteTimeGetCurrent() - lastActivityTime;
if (cost > 0.1) { // 超过 100ms 视为卡顿
NSLog(@"Runloop 卡顿:%f ms", cost * 1000);
}
}
});
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
}
总结
- Runtime 是 Objective-C 动态特性的底层支撑,通过操作类、对象、方法实现灵活的运行时行为(如方法转发、混淆),常用于框架开发、无侵入式功能扩展。
- Runloop 是线程事件管理的核心,通过循环处理事件实现线程保活、事件调度,是 UI 响应、定时器、网络回调等功能的基础。
- 两者共同构成了 iOS 应用的底层运行机制,理解它们有助于深入掌握 Objective-C 语言特性及系统运行原理。