由浅入深,从基本概念到源码解析,带你全面理解 Objective-C 运行时机制
一、什么是 Runtime? 1.1 从「面向对象」说起 在 C++ 这类静态语言中,方法的调用在编译期就确定了,编译器会把方法调用翻译成确定的函数地址。而 Objective-C 是一门「消息型」语言,方法调用在编译期只是生成了「发消息」的代码,真正要调用哪个方法,要到运行期 才能确定。
这种「推迟到运行时再决定」的能力,就是 Runtime 的精髓。
1.2 Runtime 是什么? Runtime 是 Objective-C 的运行时系统,是一套用 C 和汇编实现的底层库。它负责:
类与对象的创建、布局
方法的查找、派发(Message Dispatch)
消息传递机制(Message Passing)
动态添加/修改类、方法、属性
方法交换(Method Swizzling)
KVO、KVC、Block 等特性的底层支撑
可以理解为:Runtime 是 Objective-C 的「操作系统」 ,没有它,OC 就无法运行。
1.3 两种版本
版本
说明
适用场景
Legacy Runtime
较早版本
32 位 macOS、老设备
Modern Runtime
当前主流
64 位 macOS、iOS、模拟器
Modern Runtime 支持:非 fragile instance variables、属性自动合成、Objective-C 2.0 语法等。
二、核心概念 2.1 对象(Object)与类(Class) 在 C 中,结构体就是「数据 + 布局」。在 OC 中,对象 本质上是一个指向结构体的指针,结构体第一个成员是指向 类对象(Class) 的指针 isa。
1 2 3 4 5 6 7 8 9 10 11 12 13 struct objc_object { Class isa; }; struct objc_class { Class isa; Class superclass; }; typedef struct objc_object *id ;typedef struct objc_class *Class;
实例对象 :isa → 类对象
类对象 :isa → 元类(metaclass)
元类 :isa → 根元类,superclass → 父类的元类
2.2 消息传递(Message Passing) OC 中「调用方法」实际上是「发消息」:
1 2 3 [person sayHello]; objc_msgSend(person, @selector (sayHello));
objc_msgSend 是 Runtime 提供的 C 函数,流程大致为:
检查 receiver 是否为 nil(若为 nil,直接返回,不崩溃)
从 receiver 的 isa 指向的类中查找方法
若未找到,沿 superclass 链向上查找
找到后跳转执行实现(IMP)
若最终未找到,进入「消息转发」流程
2.3 SEL、IMP、Method
类型
说明
示例
SEL
方法选择器,方法名的唯一标识
@selector(sayHello)
IMP
函数指针,方法的实际实现
void (*)(id, SEL, ...)
Method
方法结构体,包含 SEL 和 IMP
struct objc_method
1 2 3 4 5 6 7 typedef struct objc_method *Method;struct objc_method { SEL method_name; char *method_types; IMP method_imp; };
三、消息查找与转发 3.1 方法查找流程(快速查找 + 慢速查找) 1 2 3 4 5 6 7 8 9 10 objc_msgSend(receiver, sel) │ ├─ 1. 检查 receiver 是否为 nil │ ├─ 2. 从类/父类缓存中查找 (objc_cache) —— 快速路径 │ └─ 3. 缓存未命中 → 调用 lookUpImpOrForward 慢速查找 ├─ 在当前类 method_list 中查找 ├─ 沿 superclass 链向上查找 └─ 若仍未找到 → 进入动态方法解析
3.2 动态方法解析(Dynamic Method Resolution) 在找不到方法时,Runtime 会先给类一次「补救」机会:
1 2 3 4 5 6 7 8 9 10 11 + (BOOL )resolveInstanceMethod:(SEL)sel { if (sel == @selector (dynamicMethod)) { class_addMethod([self class ], sel, (IMP)dynamicMethodIMP, "v@:" ); return YES ; } return [super resolveInstanceMethod:sel]; } void dynamicMethodIMP(id self , SEL _cmd) { NSLog (@"动态添加的方法被调用了" ); }
3.3 消息转发(Message Forwarding) 若动态解析也返回 NO,则进入转发流程:
Fast Forwarding :- (id)forwardingTargetForSelector:(SEL)aSelector
返回一个能响应该 Selector 的对象,消息将转给该对象
不修改方法签名,性能较好
Normal Forwarding :- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector + - (void)forwardInvocation:(NSInvocation *)anInvocation
返回方法签名,再在 forwardInvocation: 中处理
可灵活重定向、修改参数、多对象分发等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (id )forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector (sayHello)) { return self .backupObject; } return [super forwardingTargetForSelector:aSelector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector (sayHello)) { return [NSMethodSignature signatureWithObjCTypes:"v@:" ]; } return [super methodSignatureForSelector:aSelector]; } - (void )forwardInvocation:(NSInvocation *)anInvocation { if (anInvocation.selector == @selector (sayHello)) { [anInvocation invokeWithTarget:self .backupObject]; } else { [super forwardInvocation:anInvocation]; } }
3.4 流程小结 1 2 3 4 5 6 7 8 9 10 11 12 13 查找 IMP │ ├─ 1. 缓存命中 → 直接调用 │ ├─ 2. 当前类及父类 method_list 中找到 → 写入缓存并调用 │ ├─ 3. 未找到 → resolveInstanceMethod / resolveClassMethod │ ├─ 4. 解析失败 → forwardingTargetForSelector │ ├─ 5. 返回 nil → methodSignatureForSelector + forwardInvocation │ └─ 6. 仍无法处理 → doesNotRecognizeSelector: crash
四、Runtime 源码结构 4.1 主要头文件 1 2 3 4 5 6 7 8 objc4 源码(可在 opensource.apple.com 获取): ├── objc-runtime.mm # 运行时初始化、类加载 ├── objc-msg-x86_64.s # objc_msgSend 汇编实现(x86_64) ├── objc-msg-arm64.s # objc_msgSend 汇编实现(ARM64) ├── objc-class.mm # 类结构、方法列表 ├── objc-object.mm # 对象、isa、关联对象 ├── message.mm # 消息查找、转发 └── runtime.mm # 动态创建类、方法等
4.2 objc_object 与 isa 1 2 3 4 5 6 7 8 9 struct objc_object {private: isa_t isa; public: Class getIsa () ; void initIsa (Class cls) ; };
4.3 objc_class 结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct objc_class : objc_object { Class superclass; cache_t cache; class_data_bits_t bits; class_rw_t *data () const { return bits.data(); } const method_array_t methods () const ; const property_array_t properties () const ; const protocol_array_t protocols () const ; };
4.4 方法缓存(cache_t) 为提高查找效率,每个类都有方法缓存。查找顺序:先查缓存,再查方法列表 。
1 2 3 4 5 6 7 8 9 10 11 struct cache_t { bucket_t *buckets; mask_t mask; mask_t occupied; }; struct bucket_t { SEL sel; IMP imp; };
五、常用 Runtime API 5.1 类与对象 1 2 3 4 5 6 7 8 9 10 11 Class cls = [MyObject class ]; Class cls2 = object_getClass(obj); id obj = class_createInstance(cls, 0 );BOOL isMeta = class_isMetaClass(cls);BOOL isMember = [obj isMemberOfClass:[MyObject class ]];BOOL isKind = [obj isKindOfClass:[NSObject class ]];
5.2 方法操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Method m = class_getInstanceMethod(cls, @selector (sayHello)); SEL sel = method_getName(m); IMP imp = method_getImplementation(m); Method m1 = class_getInstanceMethod(cls, @selector (methodA)); Method m2 = class_getInstanceMethod(cls, @selector (methodB)); method_exchangeImplementations(m1, m2); class_addMethod(cls, @selector (newMethod), (IMP)newMethodIMP, "v@:" );
5.3 属性与成员变量 1 2 3 4 5 6 7 8 9 10 11 unsigned int count;objc_property_t *properties = class_copyPropertyList(cls, &count); Ivar *ivars = class_copyIvarList(cls, &count); for (unsigned int i = 0 ; i < count; i++) { Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); const char *type = ivar_getTypeEncoding(ivar); }
5.4 动态创建类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Class newClass = objc_allocateClassPair([NSObject class ], "MyDynamicClass" , 0 ); class_addIvar(newClass, "title" , sizeof (NSString *), log2(sizeof (NSString *)), @encode (NSString *)); class_addMethod(newClass, @selector (doSomething), (IMP)doSomethingIMP, "v@:" ); objc_registerClassPair(newClass); id instance = [[newClass alloc] init];
六、Method Swizzling(方法交换) 6.1 基本原理 通过 method_exchangeImplementations 交换两个方法的 IMP,使调用 A 时实际执行 B 的实现。常用于:
无侵入式 Hook 系统或第三方方法
统计埋点、日志
修复 Bug、兼容旧版本
6.2 基础写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + (void )load { static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ Class cls = [UIViewController class ]; SEL originalSel = @selector (viewDidLoad); SEL swizzledSel = @selector (swizzled_viewDidLoad); Method originalMethod = class_getInstanceMethod(cls, originalSel); Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel); method_exchangeImplementations(originalMethod, swizzledMethod); }); } - (void )swizzled_viewDidLoad { NSLog (@"ViewController viewDidLoad 被调用" ); [self swizzled_viewDidLoad]; }
6.3 安全写法:处理子类未实现的情况 若子类未重写 viewDidLoad,class_getInstanceMethod 会拿到父类的方法。直接交换会导致:父类方法被交换,影响所有子类。更安全的做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + (void )load { static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ Class cls = [UIViewController class ]; SEL originalSel = @selector (viewDidLoad); SEL swizzledSel = @selector (swizzled_viewDidLoad); Method originalMethod = class_getInstanceMethod(cls, originalSel); Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel); BOOL didAdd = class_addMethod(cls, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAdd) { class_replaceMethod(cls, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); }
6.4 实际应用:全局统计页面访问 1 2 3 4 5 6 7 8 9 10 11 + (void )load { [self swizzleInstanceMethod:@selector (viewDidAppear:) withMethod:@selector (track_viewDidAppear:)]; } - (void )track_viewDidAppear:(BOOL )animated { [self track_viewDidAppear:animated]; [Analytics trackPageView:NSStringFromClass ([self class ])]; }
七、关联对象(Associated Objects) 7.1 为什么需要关联对象? 分类(Category)不能添加实例变量,但我们有时需要给已有类「挂」一些额外数据。关联对象可以在不修改原类的情况下,为实例绑定键值对 。
7.2 API 1 2 3 4 5 6 7 8 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);id objc_getAssociatedObject(id object, const void *key);void objc_removeAssociatedObjects(id object);
7.3 内存策略(policy)
Policy
对应属性修饰符
说明
OBJC_ASSOCIATION_ASSIGN
assign
弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC
nonatomic, strong
强引用,非原子
OBJC_ASSOCIATION_RETAIN
atomic, strong
强引用,原子
OBJC_ASSOCIATION_COPY_NONATOMIC
nonatomic, copy
拷贝,非原子
OBJC_ASSOCIATION_COPY
atomic, copy
拷贝,原子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (void )addAction:(void (^)(UIButton *sender))block forControlEvents:(UIControlEvents )events; #import <objc/runtime.h> static const void *kButtonBlockKey = &kButtonBlockKey;- (void )addAction:(void (^)(UIButton *))block forControlEvents:(UIControlEvents )events { objc_setAssociatedObject(self , kButtonBlockKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC); [self addTarget:self action:@selector (block_buttonTapped:) forControlEvents:events]; } - (void )block_buttonTapped:(UIButton *)sender { void (^block)(UIButton *) = objc_getAssociatedObject(self , kButtonBlockKey); if (block) block(sender); }
八、实际项目中的应用案例 8.1 场景一:无侵入埋点 通过 Method Swizzling Hook viewDidAppear: 和 viewDidDisappear:,自动统计页面停留时长,无需在每个 VC 里手动写埋点代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (void )tracked_viewDidAppear:(BOOL )animated { [self tracked_viewDidAppear:animated]; self .pageEnterTime = CACurrentMediaTime (); [Tracking logEvent:@"page_enter" properties:@{@"page" : NSStringFromClass ([self class ])}]; } - (void )tracked_viewDidDisappear:(BOOL )animated { NSTimeInterval duration = CACurrentMediaTime () - self .pageEnterTime; [Tracking logEvent:@"page_leave" properties:@{ @"page" : NSStringFromClass ([self class ]), @"duration" : @(duration) }]; [self tracked_viewDidDisappear:animated]; }
8.2 场景二:防崩溃( unrecognized selector) 利用消息转发,在 forwardInvocation: 中统一处理未实现的方法调用,记录日志并优雅降级,避免 Crash。
1 2 3 4 5 6 7 8 9 10 11 12 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *sig = [super methodSignatureForSelector:aSelector]; if (!sig) { sig = [NSMethodSignature signatureWithObjCTypes:"v@:" ]; } return sig; } - (void )forwardInvocation:(NSInvocation *)anInvocation { [CrashGuard logUnrecognizedSelector:anInvocation.selector onObject:self ]; }
8.3 场景三:字典转模型(JSON → Model) 遍历类的属性列表,根据属性名从字典中取值并赋值,实现自动 JSON 转 Model。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + (instancetype )modelWithDictionary:(NSDictionary *)dict { id model = [[self alloc] init]; unsigned int count; objc_property_t *properties = class_copyPropertyList([self class ], &count); for (unsigned int i = 0 ; i < count; i++) { const char *name = property_getName(properties[i]); NSString *key = [NSString stringWithUTF8String:name]; id value = dict[key]; if (value && ![value isKindOfClass:[NSNull class ]]) { [model setValue:value forKey:key]; } } free(properties); return model; }
8.4 场景四:KVO 防崩溃 KVO 常见崩溃:未配对 removeObserver、重复 remove、对象已释放。可通过 Hook addObserver:forKeyPath:options:context: 和 removeObserver:forKeyPath:,用关联对象维护观察者链表,在 dealloc 时自动移除,实现「自释放」KVO。
8.5 场景五:调试时打印对象属性 利用 class_copyIvarList 和 class_copyPropertyList 遍历所有属性,valueForKey: 取值并拼接成字符串,方便调试时查看对象完整状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (NSString *)debugDescription { NSMutableString *desc = [NSMutableString stringWithFormat:@"<%@: %p>\n" , [self class ], self ]; unsigned int count; Ivar *ivars = class_copyIvarList([self class ], &count); for (unsigned int i = 0 ; i < count; i++) { Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; id value = [self valueForKey:key]; [desc appendFormat:@" %@ = %@\n" , key, value]; } free(ivars); return desc; }
九、注意事项与最佳实践 9.1 线程安全
objc_msgSend 等查找流程内部有锁,但动态修改类结构 (如 class_addMethod)需注意线程安全
Method Swizzling 建议在 +load 中、单次执行完成,避免并发问题
9.2 Swizzling 陷阱
只在 +load 中执行,且用 dispatch_once 保证只执行一次
注意子类未实现父类方法时的交换范围
避免多个库对同一方法重复 Swizzling,易产生难以排查的 Bug
9.3 性能考虑
Runtime 动态特性有开销,高频路径慎用
方法缓存使大部分调用命中缓存,性能可接受
关联对象查找为哈希查找,量不大时影响较小
9.4 Swift 与 Runtime
Swift 类继承自 NSObject 时,仍可使用 Runtime
纯 Swift 类(不继承 NSObject)使用更静态的派发方式,Runtime 能力受限
若需在 Swift 中使用 Runtime,需将类/方法标记为 @objc 或 dynamic
十、总结
主题
要点
本质
OC 是消息型语言,方法调用 = 发消息,由 Runtime 在运行期决定实际执行的 IMP
查找
缓存 → 当前类 → 父类 → 动态解析 → 快速转发 → 完整转发 → 崩溃
Swizzling
交换 IMP,实现无侵入 Hook,注意子类与并发
关联对象
为实例动态绑定数据,弥补 Category 不能加实例变量的限制
应用
埋点、防崩溃、JSON 转模型、调试工具等
理解 Runtime,不仅有助于排查「消息转发」「KVO 崩溃」等问题,还能在需要时写出更灵活、可扩展的架构。建议结合 objc4 源码 和实际项目实践,逐步加深理解。