1. RunTime简介
RunTime(运行时)是指程序在运行过程中动态管理类型、对象、方法等的机制。Objective-C 和 Swift 都拥有自己的运行时系统,但设计理念和实现方式有很大不同。理解 RunTime 的底层原理,是掌握 iOS 高级开发的关键。
2. Objective-C RunTime底层原理
2.1 对象结构与isa指针
2.1.1 OC对象的本质
Objective-C 对象在底层以结构体的形式实现,其最核心的成员是 isa 指针。
cpp
struct objc_object {
Class isa;
};
- objc_object 是所有 OC 对象的基类结构体,只包含 isa 指针。
- 每个实例对象的内存布局:isa 指针 + 父类成员变量 + 本类成员变量(由编译器自动生成)。
- Class 实际上是 objc_class 类型,指向类对象。
- 每个对象通过 isa 指针找到自己的类对象,类对象再通过 isa 指针找到元类对象。
2.1.2 isa指针的优化(isa_t)
在64位系统中,为了最大限度利用指针的64位空间,苹果将 isa指针设计为联合体 union isa_t。该结构不仅存储对象所属类的地址,还通过位域技术将内存管理与对象特征标志位压缩在同一指针空间内,从而显著减少内存占用并提升访问效率。
cpp
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; // 原始类指针(未启用优化时)
uintptr_t bits; // 位域访问入口
struct { // 位域结构(arm64架构示例)
uintptr_t nonpointer : 1; // 是否启用指针优化
uintptr_t has_assoc : 1; // 是否含关联对象
uintptr_t has_cxx_dtor : 1; // 是否有C++析构函数
uintptr_t shiftcls : 33; // 类地址(MACH_VM_MAX_ADDRESS 0x1000000000)
uintptr_t magic : 6; // 对象生命周期校验
uintptr_t weakly_referenced : 1; // 是否被弱引用
uintptr_t deallocating : 1; // 是否正在释放
uintptr_t has_sidetable_rc : 1; // 是否使用sideTable存储引用计数
uintptr_t extra_rc : 19; // 快速引用计数存储(溢出后转存至sideTable)
};
};
2.1.3 Tagged Pointer
- Tagged Pointer是一种独立于isa优化的内存优化技术,通过将小对象数据直接编码在指针值中来实现内存优化。
- 其基本原理是将特定小对象(如NSNumber、NSDate、短字符串等)的数据直接存储在指针值中,并将指针最高位置1作为Tagged Pointer标志。
- 它的优势在于无需实例化对象和通过isa指针访问类信息,从而显著减少内存分配和访问开销。
2.1.4 OC对象的内存布局
实际内存布局顺序为:
- isa指针(对象头部,8字节/4字节,取决于架构)
- 父类的成员变量(从NSObject/父类开始,依次向下排列)
- 本类声明的成员变量(按声明顺序排列)
以下代码为例:
objectivec
@interface Animal : NSObject {
int age;
int height;
}
@end
@interface Dog : Animal {
int weight;
int color;
}
@end
Dog对象的内存布局:
| isa | age | height | weight | color |
|-----|-------|----------|-----------|--------|
- isa:指向类对象
- age、height:Animal父类成员变量
- weight、color:Dog本类成员变量
这样设计可以保证子类对象在作为父类对象使用时,父类成员变量的偏移量不变,从而保证继承的兼容性和对象模型的稳定性。
2.2 类对象与元类对象
2.2.1 类对象(Class)
1. 类对象基本结构
类对象本质也是一个结构体,定义如下(简化版):
cpp
struct objc_class : objc_object {
Class isa;
Class super_class;
cache_t cache;
class_data_bits_t bits;
};
- isa:指向元类对象
- super_class:指向父类对象
- cache:方法缓存
- class_data_bits_t:存储方法列表、属性列表、协议列表等
2. class_data_bits_t
class_data_bits_t是Objective-C运行时中用于高效存储和访问类数据的关键结构.
cpp
struct class_data_bits_t {
uintptr_t bits; // 存储数据的位域
// 获取class_rw_t指针
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 设置class_rw_t指针
void setData(class_rw_t *newData) {
bits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
}
};
其中class_rw_t存储运行时可变数据,class_ro_t存储编译期确定的只读数据。其结构如下
cpp
struct class_rw_t {
uint32_t flags; // 标志位
uint32_t version; // 版本号
const class_ro_t *ro; // 指向只读数据
method_list_t *methods; // 方法列表
property_list_t *properties; // 属性列表
protocol_list_t *protocols; // 协议列表
ivar_list_t *ivars; // 成员变量列表
......
};
struct class_ro_t {
uint32_t flags; // 标志位
uint32_t instanceStart; // 实例对象起始偏移
uint32_t instanceSize; // 实例对象大小
const char *name; // 类名
method_list_t *methods; // 方法列表
property_list_t *properties; // 属性列表
protocol_list_t *protocols; // 协议列表
ivar_list_t *ivars; // 成员变量列表
......
};
2.2.2 元类对象(Meta-Class)
- 元类对象用于存储类方法的实现,本质上类方法就是元类的实例方法。
- 元类的isa指向根元类(NSObject的Meta-Class)
- 元类的super_class指向父类的元类
- 根元类的isa指针指向自己
2.2.3 内存结构图

- 类对象和元类对象本质上都是objc_class结构体
- 类对象存储实例方法,元类对象存储类方法
2.3 方法缓存与方法列表
2.3.1 方法缓存(cache_t)
每个类对象都有一个方法缓存cache,结构如下:
cpp
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
- _buckets:哈希表,存储 SEL(方法选择子)和 IMP(实现指针)的映射关系。
- _mask、_occupied:用于哈希冲突处理
作用:加速方法查找,避免每次都遍历方法列表。
2.3.2 方法列表
类对象中有方法列表,存储所有实例方法:
cpp
struct method_t {
SEL name; // 方法名
const char *types; // 方法类型编码
IMP imp; // 方法实现指针
};
- 方法查找顺序:先查缓存,再查方法列表,最后查父类
2.4 成员变量与属性
- 成员变量(Ivar)
在类对象的 ivar_list 中,保存的是所有成员变量的描述信息(如名字、类型、偏移量等),而成员变量的实际值是存储在每个实例对象的内存空间中(紧跟在 isa 指针之后,按照继承链和声明顺序排列)。
- 属性(Property)
属性信息存储在类对象的 property_list 中,仅用于描述属性的名称、类型、特性等。属性通常会自动合成一个对应的成员变量(如 name),成员变量的实际值存储在实例对象中。
cpp
// 成员变量描述结构体
struct ivar_t {
int32_t *offset; // 偏移量(成员变量在实例对象内存中的位置)
const char *name; // 成员变量名
const char *type; // 类型编码
// ... 其他信息
};
// 成员变量列表
struct ivar_list_t {
uint32_t count; // 成员变量数量
ivar_t ivars[]; // 成员变量数组
};
// 属性描述结构体
struct property_t {
const char *name; // 属性名
const char *attributes; // 属性特性字符串(如T@"NSString",C,N,V_name等)
// ... 其他信息
};
// 属性列表
struct property_list_t {
uint32_t count; // 属性数量
property_t properties[];// 属性数组
};
3. Objective-C消息机制与方法查找------RunTime的灵魂
3.1 objc_msgSend流程
Objective-C 的方法调用本质上是"向对象发送消息",而 objc_msgSend 就是消息发送的核心函数。理解其底层流程,是掌握 RunTime 的关键。
1. 空对象判断(Nil Check)
- 首先判断消息接收者(对象指针)是否为 nil。
- 如果是 nil,objc_msgSend 直接返回 0,不做任何操作(这也是 OC 调用 nil 对象不会崩溃的原因)。
2. 获取类对象(Class Lookup)
- 通过对象头部的 isa 指针,获取当前对象的类对象(Class)。
3. 方法缓存查找(Cache Lookup)
- 优先在类对象的缓存(cache_t)中查找方法实现(IMP)。
- 如果缓存命中,直接跳转到对应的 IMP 执行,速度极快。
4. 方法列表查找(Method List Lookup)
- 如果缓存未命中,则遍历类对象的方法列表(method_list_t),查找与 SEL(方法选择子)匹配的方法。
- 找到后,将 SEL 和 IMP 加入缓存,便于下次快速查找。
5. 父类链查找(Superclass Lookup)
- 如果当前类的方法列表中仍未找到目标方法,则顺着 super_class 指针,递归查找父类的方法缓存和方法列表,直到 NSObject 为止。
6. 动态方法解析(Dynamic Method Resolution)
- 如果在整个继承链上都找不到目标方法,RunTime 会尝试动态方法解析。
- 通过调用 +resolveInstanceMethod: 或 +resolveClassMethod:,允许开发者动态添加方法实现。
- 如果在回调中通过 class_addMethod 成功添加了方法,RunTime 会重新走一次方法查找流程。
7. 消息转发机制(Message Forwarding)
- 如果动态方法解析仍未能处理该消息,进入消息转发阶段:
- 首先调用 -forwardingTargetForSelector:,允许指定其他对象响应该消息。
- 如果还未处理,则调用 -methodSignatureForSelector: 和 -forwardInvocation:,开发者可自定义消息处理逻辑。
8. 抛出异常(Unrecognized Selector)
- 如果所有机制都未能处理该消息,RunTime 最终会抛出 unrecognized selector sent to instance 异常,导致程序崩溃。
3.2 消息转发三步曲
当对象接收到一个未实现的方法调用时,RunTime 会依次经历以下步骤:
- 动态方法解析(resolveInstanceMethod:、resolveClassMethod:)
- 快速消息转发(forwardingTargetForSelector:)
- 完整消息转发(methodSignatureForSelector: & forwardInvocation:)
下面我们逐步拆解每一步的底层实现。
3.2.1 动态方法解析(resolveInstanceMethod、resolveClassMethod)
底层原理:
当实例方法找不到时,RunTime 会调用 +resolveInstanceMethod:;类方法找不到时,调用 +resolveClassMethod:。你可以在这里通过 class_addMethod 动态添加方法。
代码示例:
objectivec
// 动态添加实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
class_addMethod(self, sel, (IMP)testIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 动态添加类方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(testClassMethod)) {
Class metaClass = object_getClass(self); // 获取元类
class_addMethod(metaClass, sel, (IMP)testClassMethodIMP, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
- 如果返回 YES,RunTime 会重新查找方法并执行。
- 如果返回 NO 或未处理,进入下一步。
3.2.2 快速消息转发(forwardingTargetForSelector:)
底层原理:
如果动态方法解析未能处理,RunTime 会调用实例方法 -forwardingTargetForSelector:。你可以返回一个"备选接收者",RunTime 会将消息直接转发给该对象。如果返回 nil,进入完整消息转发。
代码示例:
objectivec
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return anotherObj; // 转发给 anotherObj
}
return [super forwardingTargetForSelector:aSelector];
}
3.2.3 获取方法签名(methodSignatureForSelector:)
底层原理:
如果快速转发未处理,RunTime 会调用 -methodSignatureForSelector:,询问你"这个 SEL 对应的方法签名是什么?"你需要返回一个 NSMethodSignature 对象,描述方法的参数和返回值类型。如果返回 nil,RunTime 会直接抛出异常。
代码示例:
objectivec
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
3.2.4 自定义消息处理(forwardInvocation:)
底层原理:
RunTime 会根据方法签名创建一个 NSInvocation 对象,封装原始的消息调用。然后调用 -forwardInvocation:,你可以在这里自定义如何处理这个消息,比如转发给其他对象、修改参数、记录日志等。如果你没有处理,调用 [super forwardInvocation:],最终会抛出异常。
代码示例:
objectivec
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([anotherObj respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:anotherObj];
} else {
[super forwardInvocation:anInvocation];
}
}
3.3 抛出异常(unrecognized selector)
- 底层原理
如果上述所有机制都未能处理该消息,RunTime 会调用 doesNotRecognizeSelector:,最终抛出 unrecognized selector sent to instance 异常,导致程序崩溃。
- 异常信息解读
当出现此异常时,Xcode控制台会输出类似如下信息:
cpp
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[MyClass test]: unrecognized selector sent to instance 0x12345678'
- MyClass:出错的类名
- test:未识别的方法名
- 0x12345678:对象内存地址
4. Swift RunTime机制深度剖析
4.1 Swift对象模型:值类型与引用类型的本质
4.1.1 值类型与引用类型的区别
在Swift中,对象主要分为两种类型:值类型和引用类型。这两种类型在内存管理和使用方式上有着本质的区别:
- 值类型:包括struct和enum,数据直接存储在变量本身,通常在栈上分配,内存布局紧凑,访问效率高。赋值时进行值拷贝。
- 引用类型:主要指class,数据存储在堆上,变量保存的是指向对象的指针。支持继承和多态,赋值时传递引用。
4.1.2 Swift类对象的内存结构
Swift的类对象采用了现代化的内存布局设计,其结构如下:
cpp
| Metadata指针 | 属性1 | 属性2 | ... |
- Metadata指针:指向类型的元数据,类似于Objective-C的isa指针,但包含更丰富的信息
- 属性区:按顺序存储对象的所有属性值
4.1.3 继承NSObject的Swift类的内存结构
继承NSObject的Swift类同时具有Swift和Objective-C的特征
以下代码为例:
Swift
class MyClass: NSObject {
var property1: Int
var property2: String
}
其内存布局如下所示:
cpp
+------------------+------------------+------------------+------------------+
| isa指针(OC) | Metadata指针(Swift) | property1 | property2 |
+------------------+------------------+------------------+------------------+
- 对象头部包含两个指针: Objective-C的isa指针和Swift的Metadata指针。
- 属性按照Swift的内存布局规则排列。
- 方法根据是否@objc决定存储位置,objc方法:存储在Objective-C的class_rw_t中,非@objc方法:存储在Swift的虚表中。
这种混合结构让继承NSObject的Swift类既能保持Swift的高效,又能与Objective-C无缝交互。
4.1.4 Metadata元数据详解
Metadata是Swift类型系统的核心,它记录了类型在运行时的所有关键信息。以类(Class)为例,其简化结构如下:
以类(Class)为例,简化结构如下:
cpp
struct ClassMetadata {
void *kind; // 类型标识(区分class/struct/enum等)
void *superclass; // 父类的Metadata指针
void *cacheData; // 方法缓存(加速查找)
void *data; // 指向更详细的类型描述
// ... 其他字段
};
Metadata采用分层设计:基础信息在前,详细信息通过指针层层递进。
-
基础层:包含类型标识、父类信息等基本信息
-
扩展层:过data指针指向更详细的信息,包括:
- vtable(虚函数表):存储类中所有可重写的方法实现,包括类自身定义的方法和实现协议的方法,每个类只有一个虚函数表,在编译期确定且不可修改,主要用于普通方法调用和继承场景。
- witness table(协议表):存储协议本身定义的方法和协议扩展中的方法实现,每个类型对每个协议都有一个协议表,支持运行时修改和动态替换实现,主要用于协议方法的调用和协议扩展场景。
- 属性描述:属性名、类型、偏移量等
- 泛型信息:泛型参数的类型描述
4.2 Swift方法调用机制:静态与动态的平衡
4.2.1 静态派发:Swift的默认选择
Swift默认采用静态派发机制,这意味着:
- 方法调用在编译期就确定了调用地址。调用时无需查找,直接跳转
- 适用于struct、final class、private方法等场景
- 执行效率高,接近C语言的函数调用
4.2.2 动态派发:特定场景的选择
在以下情况下,Swift会采用动态派发机制:
- 被dynamic修饰的方法
- 遵循@objc协议的方法
- 继承自NSObject的类中的方法
- 协议的可选方法
动态派发通过vtable(虚函数表)或witness table(协议表)实现:
cpp
+-------------------+
| 方法1的指针(IMP) |
| 方法2的指针(IMP) |
| ... |
+-------------------+
4.2.3 方法查找流程对比
- 静态派发:编译期确定方法地址,直接调用方法实现,无需运行时查找。
- 动态派发:运行时查找vtable/witness table,获取方法实现指针,调用方法实现。
4.2.4 与Objective-C的对比
- OC:所有方法调用都走objc_msgSend,完全动态派发,灵活性高,但性能开销大。
- Swift:优先使用静态派发,仅在必要时使用动态派发,性能更优,但动态性受限。
4.3 Swift的反射与元编程能力
4.3.1 Mirror反射API
Swift提供了Mirror类型用于运行时反射,可以获取对象的属性名、属性值、类型等信息。
Swift
let mirror = Mirror(reflecting: someObject)
for child in mirror.children {
print("属性名: \(child.label), 属性值: \(child.value)")
}
- 只能读取对象信息,不能修改。
- 支持获取属性名、属性值、类型等信息,使用简单,但功能有限。
4.3.2 Swift RunTime的局限性
- 无法动态添加/替换方法:没有公开的RunTime C API
- 类型信息有限:大部分类型信息在编译期丢失
- 元编程能力弱:不支持元类、动态创建类等高级特性
4.3.3 Swift RunTime的设计哲学与优化
- 类型安全:所有类型信息尽量在编译期确定,避免运行时错误
- 极致性能:静态派发、紧凑内存布局、去虚表等优化
- 安全性高:避免了OC RunTime带来的诸多安全隐患
总结一句话:
Swift 的 RunTime 机制以牺牲部分动态性为代价,换来了更高的类型安全和运行效率。
5. Objective-C与Swift RunTime机制对比
5.1 核心机制对比
Objective-C RunTime | Swift RunTime | |
---|---|---|
对象头部 | isa指针,部分信息存side table | Metadata指针,引用计数直接存对象头部 |
方法派发 | 动态派发(objc_msgSend ),全部运行时查找 |
静态派发为主,动态派发仅限特殊场景 |
动态性 | 极强,支持动态添加/替换方法、类、属性等 | 较弱,类型和方法多在编译期确定 |
反射能力 | 强大,支持完整反射和元编程 | Mirror API有限,无法动态修改类型结构 |
内存布局 | 对象头部+成员变量,side table存扩展信息 | Metadata指针+属性区,布局紧凑 |
弱引用 | 全局weak表,side table辅助 | 全局weak表,直接与对象头部配合 |
关联对象 | 支持(依赖side table) | 不支持(标准库无此机制) |
性能 | 动态性带来一定性能损耗 | 静态派发、内存优化,性能极高 |
安全性 | 动态性强但易出错 | 类型安全,编译期检查,运行时更安全 |
混编支持 | 与C/Swift无缝混编 | 与OC混编需继承NSObject ,部分特性受限 |
5.2 方法派发机制
-
Objective-C 所有方法调用都通过objc_msgSend来实现,也就是每次调用方法时,系统都会在运行时查找方法的具体实现。这种方式非常灵活,可以在运行时动态替换方法,但也带来了一定的性能损耗。
-
Swift采用以静态派发为主的混合派发机制。大多数方法在编译期就确定了实现位置,只有少数特殊情况(如被dynamic修饰的方法、遵循@objc协议的方法、协议的可选方法、继承自NSObject的类的方法)才会使用动态派发。动态派发通过vtable或witness table实现,虽然限制了动态性,但大大提升了性能。
5.3 动态性与反射
-
Objective-C具备极强的动态性。开发者可以在运行时动态添加或替换方法、创建新类、修改类结构,还可以通过完整的反射API查询和操作类型信息。这些特性使其非常适合实现AOP、热修复等高级功能。例如,可以通过class_addMethod动态添加方法,通过method_exchangeImplementations交换方法实现。
-
Swift的动态性相对较弱。大多数类型信息在编译期就已确定,运行时修改能力有限。虽然提供了Mirror API用于查看对象属性和类型信息,但无法动态修改类型结构。这种设计虽然限制了某些高级特性的实现,但更适合类型安全的业务开发。例如,可以通过Mirror(reflecting: object)查看对象属性。
5.4 内存布局与side table
-
Objective-C的对象内存布局包含isa指针(8字节)和按声明顺序排列的成员变量。扩展信息(如引用计数、弱引用、关联对象、方法缓存等)存储在side table中。这种设计虽然减少了对象本身的内存占用,但访问这些信息需要额外查找side table,增加了访问成本。
-
Swift的对象内存布局包含Metadata指针和按声明顺序排列的属性。引用计数直接存储在对象头部,弱引用与对象头部配合,没有公开的side table机制。这种设计虽然增加了对象本身的大小,但减少了访问开销,整体内存布局更紧凑高效。
5.5 实际开发影响
-
在框架开发中,Objective-C更适合需要高度动态性的场景,如AOP实现、热修复系统、插件化架构、动态代理、KVO/KVC等。可以通过减少消息发送、缓存方法实现、优化内存布局、合理使用side table等方式优化性能。
-
Swift更适合高性能业务逻辑、类型安全API、并发编程、函数式编程等场景。可以通过利用静态派发、优化值类型使用、减少动态派发、优化内存布局等方式提升性能。在混编开发中,需要明确边界、合理分工、注意性能、统一规范,根据具体需求选择合适的语言或混编方案。
6. RunTime的实际应用场景与代码示例
6.1 Objective-C RunTime常见用法
6.1.1 方法交换(Method Swizzling)
场景:无侵入埋点、AOP、日志等。
示例:统计所有UIViewController的viewDidLoad调用。
objectivec
#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method original = class_getInstanceMethod(self, @selector(viewDidLoad));
Method swizzled = class_getInstanceMethod(self, @selector(swizzled_viewDidLoad));
method_exchangeImplementations(original, swizzled);
});
}
- (void)swizzled_viewDidLoad {
NSLog(@"%@ did load", NSStringFromClass([self class]));
[self swizzled_viewDidLoad]; // 实际调用原viewDidLoad
}
@end
6.1.2 动态添加属性(关联对象)
场景:为Category动态添加属性。
objectivec
#import <objc/runtime.h>
static const void *kNameKey = &kNameKey;
@interface Person : NSObject
@end
@implementation Person
@end
@interface Person (Category)
@property (nonatomic, strong) NSString *name;
@end
@implementation Person (Category)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, kNameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, kNameKey);
}
@end
6.1.3 自动归档与解档
场景:自动实现NSCoding协议,无需手写每个属性。
objectivec
#import <objc/runtime.h>
@implementation Person
- (void)encodeWithCoder:(NSCoder *)coder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[coder encodeObject:value forKey:key];
}
free(ivars);
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [coder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end
6.1.4 消息转发机制
场景:多重代理、消息中转、容错处理。
objectivec
// 快速消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(testMethod)) {
return self.anotherObject;
}
return [super forwardingTargetForSelector:aSelector];
}
// 完整消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(testMethod)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([self.anotherObject respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self.anotherObject];
} else {
[super forwardInvocation:anInvocation];
}
}
6.2 Swift RunTime常见用法
6.2.1 Mirror反射
场景:调试、日志、自动打印对象属性。
Swift
struct Person {
var name: String
var age: Int
}
let p = Person(name: "Tom", age: 18)
let mirror = Mirror(reflecting: p)
for child in mirror.children {
print("属性名: \(child.label ?? ""), 属性值: \(child.value)")
}
// 输出:
// 属性名: name, 属性值: Tom
// 属性名: age, 属性值: 18
6.2.2 动态派发与@objc
场景:需要与OC交互、KVO、KVC、消息转发等。
Swift
class Animal: NSObject {
@objc dynamic func run() {
print("Animal run")
}
}
class Dog: Animal {
override func run() {
print("Dog run")
}
}
let a: Animal = Dog()
a.run() // 输出 Dog run,走动态派发
总结
RunTime(运行时)是iOS开发的核心机制,主要指程序在运行过程中动态管理类型、对象、方法等的能力。Objective-C的RunTime极为强大,支持动态派发、方法交换、动态添加属性、消息转发等,底层通过isa指针、类对象、元类对象、方法缓存等结构实现,所有方法调用都走objc_msgSend,具备极高的灵活性和反射能力,适合AOP、热修复等高级玩法。Swift的RunTime则以类型安全和高性能为主,大部分方法静态派发,只有特殊场景才动态查找,反射能力有限,无法动态修改类型结构。两者对比,OC更动态、灵活,Swift更安全、高效。实际开发中,OC适合底层框架和需要动态性的场景,Swift则适合业务开发和性能敏感场合。理解RunTime原理,有助于开发者写出更高效、灵活和安全的iOS代码。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注我,后续会持续分享更多 iOS 底层原理与实战经验