📑 本文目录
1. Runtime 基础概念与核心结构
在 iOS 开发面试中,iOS Runtime面试题 几乎是必考环节。Runtime 是 Objective-C 的底层 API,它是一套 C 语言 API,通过一系列指针操作内存,实现了 Objective-C 的动态特性。理解 Runtime 是深入掌握 iOS 底层原理的关键。
1.1 什么是 Runtime?
Runtime 是一套 C 语言库,它定义了对象、类、方法等在内存中的结构。当我们在代码中调用 [obj method] 时,编译器会将其转换为 objc_msgSend(obj, @selector(method))。这种转换使得 Objective-C 具有了强大的动态性,比如可以在运行时动态创建类、修改方法实现等。
1.2 核心数据结构
在面试中,面试官常问 Runtime 的核心结构。以下是几个关键结构体:
| 结构体 | 描述 | 关键作用 |
|---|---|---|
objc_object |
对象的根结构 | 包含 isa 指针,指向类对象 |
objc_class |
类对象结构 | 包含 superclass, methods, ivars 等 |
method_t |
方法结构 | 包含 SEL, IMP, types |
ivar_list_t |
实例变量列表 | 存储类的所有成员变量 |
其中,isa 指针 是核心中的核心。在 64 位系统中,isa 指针经过优化,存储在 union 中,包含类指针、是否释放中、引用计数等位域信息。面试中常考:“isa 指向哪里?” 答案是:实例对象的 isa 指向类对象(Class),类对象的 isa 指向元类(Meta-Class),元类的 isa 指向根元类(Root Meta-Class),最终形成一个闭环。
2. 消息发送机制 (Message Sending)
消息发送是 Runtime 最核心的功能。面试中,iOS Runtime面试题 经常围绕消息发送的流程展开。理解消息发送流程,有助于理解 OC 的动态绑定和性能优化。
2.1 消息发送流程
当对象收到消息时,Runtime 会执行以下步骤:
Runtime 首先在方法缓存中查找 SEL 对应的 IMP。如果找到,直接调用,性能极高。
如果缓存未命中,Runtime 在当前类的 method list 中查找。
如果当前类未找到,递归查找父类,直到根类 NSObject。
如果全程未找到,Runtime 调用
+resolveInstanceMethod:。如果返回 YES,再次尝试发送消息。
如果未解析,调用
-doesNotRecognizeSelector:,抛出异常。
2.2 静态派发 vs 动态派发
在 C++ 中,虚函数调用是动态的;而在 C 中,函数调用是静态的。Objective-C 的消息发送是动态的,但可以通过 __attribute__((objc_direct)) 进行优化,使其像 C 函数一样静态调用,提升性能。面试中常问:“消息发送和函数调用有什么区别?” 答案是:消息发送是动态查找 IMP,函数调用是直接跳转地址。
3. 方法交换 (Method Swizzling)
iOS Runtime面试题 中,方法交换是考察动手能力的重要环节。它常用于 APM 监控、日志打印、权限控制等场景。
3.1 原理
方法交换的本质是交换两个方法的 IMP(实现指针)。通过 class_getInstanceMethod 获取方法,再用 method_exchangeImplementations 交换它们。
3.2 代码示例
// 在 +load 方法中执行,保证只执行一次
- (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSel = @selector(viewWillAppear:);
SEL swizzledSel = @selector(kaoacca_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSel);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSel);
// 先尝试添加方法,防止原方法未实现导致移除失败
BOOL didAdd = class_addMethod(class,
originalSel,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(originalMethod));
if (didAdd) {
// 如果添加成功,说明原方法不存在,将交换后的方法指向原SEL
class_replaceMethod(class,
swizzledSel,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
// 否则直接交换实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)kaoacca_viewWillAppear:(BOOL)animated {
// 调用原方法,因为 IMP 已经交换,这里实际调用的是原 viewWillAppear
[self kaoacca_viewWillAppear:animated];
NSLog(@"ViewWillAppear: %@", self);
}
3.3 注意事项
⚠️ 线程安全
方法交换必须在 +load 中执行,因为 +load 是线程安全的,且只执行一次。
⚠️ 递归调用
交换后,原方法名指向新实现,新方法名指向原实现。调用时要小心无限递归。
⚠️ 分类冲突
避免在多个分类中交换同一个方法,否则会导致不可预知的行为。
4. 动态方法解析与关联对象
除了消息发送和方法交换,Runtime 还提供了动态创建类和关联对象的能力,这也是 iOS Runtime面试题 的高频考点。
4.1 动态创建类
使用 objc_allocateClassPair 可以动态创建类,常用于动态生成 Model 类或 Mock 对象。
Class dynamicClass = objc_allocateClassPair([NSObject class], "DynamicClass", 0);
objc_registerClassPair(dynamicClass);
id obj = [[dynamicClass alloc] init];
4.2 关联对象 (Associated Objects)
分类不能添加属性,但可以通过关联对象实现属性功能。面试中常问:“如何实现分类添加属性?”
// 设置关联对象
objc_setAssociatedObject(self, @"key", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 获取关联对象
id value = objc_getAssociatedObject(self, @"key");
关联对象的底层实现是一个全局的 Hash 表,Key 是对象指针,Value 是一个关联对象结构体,包含 Key、Value、析构函数等。
5. Runtime 与内存管理
iOS Runtime面试题 也会考察 Runtime 在内存管理中的作用,特别是 ARC 和 MRC 的区别,以及弱引用表的工作原理。
5.1 ARC 原理
ARC 是编译期特性,编译器在编译时自动插入 retain、release、autorelease 调用。Runtime 提供了 objc_retain、objc_release 等函数供编译器调用。
5.2 弱引用表 (Weak Table)
ARC 下,弱指针存储在弱引用表中。当对象引用计数为 0 时,Runtime 会遍历弱引用表,将指向该对象的弱指针置为 nil,并调用析构函数。面试中常问:“为什么弱引用不会导致循环引用?” 因为弱引用不增加引用计数,且对象释放时会自动置 nil。
💡 总结与建议
掌握 iOS Runtime面试题 中的核心考点,不仅能帮助你在面试中脱颖而出,更能让你深入理解 iOS 系统的运行机制,写出更高效、更健壮的代码。建议开发者:
- 📚 深入阅读
objc_msgSend源码,理解消息发送的每一个细节。 - 🛠️ 动手实践方法交换,尝试实现一个简单的 APM 监控框架。
- 🔍 关注 Apple 官方文档,了解 Runtime 的最新特性。
- 💬 多参与技术社区讨论,与其他开发者交流心得。