Objective-C Runtime
面试回答版
一句话概括
Runtime 是 Objective-C 的动态运行时系统,它让 OC 在运行期(而非编译期)完成消息分发、方法解析和动态扩展。OC 的方法调用本质上是消息发送,由 Runtime 库负责查找和执行。
核心数据结构
对象 & 类
// 对象结构
struct objc_object {
isa_t isa; // isa 指针,指向类对象
};
// 类结构
struct objc_class : objc_object {
isa_t isa; // 指向元类
Class superclass; // 父类指针
cache_t cache; // 方法缓存
class_data_bits_t bits; // 类的方法列表、属性列表、协议列表等
};
isa指针:现代 Runtime 中是非指针 isa(non-pointer isa),利用 64 位中的空闲位存储引用计数等额外信息,减少内存访问层级。objc_class继承自objc_object,所以类本身也是一个对象。
元类 (Meta-Class)
- 实例对象的
isa→ 类对象 - 类对象的
isa→ 元类 - 元类的
isa→ 根元类(Root Meta Class)→ 指向自身 - 元类存储"类方法",当调用
[NSObject alloc]时,Runtime 在元类的方法列表中查找alloc
实例对象 ──isa──→ 类对象 ──isa──→ 元类 ──isa──→ 根元类 ──isa──→ 自身
│ │
↓ ↓
superclass superclass
│ │
↓ ↓
父类对象 根元类 ──superclass──→ 根类对象
关键理解:实例方法存在类对象中,类方法存在元类中。类可以继承,元类同样遵循继承链。
Method / SEL / IMP
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name; // 方法名(C 字符串,已唯一化)
char * _Nullable method_types; // 类型编码
IMP _Nonnull method_imp; // 函数指针
};
typedef struct objc_selector *SEL; // 方法名在 Runtime 中的唯一标识,相同名字对应同一个 SEL
typedef id (*IMP)(id, SEL, ...); // 方法实现的函数指针
- SEL:方法名在 Runtime 中的唯一标识,用
@selector()或sel_registerName()获取,相同的名字对应同一个 SEL。 - IMP:真正的函数实现指针,拿到它就可以直接调用。
- Method:SEL 和 IMP 的配对关系,外加类型编码描述参数/返回值类型。
class_rw_t / class_ro_t
这两个结构体不直接属于 objc_class 的成员,而是通过 objc_class 的 class_data_bits_t bits 间接访问:
objc_class
├─ isa
├─ superclass
├─ cache
└─ bits ───→ class_rw_t(类 realize 后创建,可读写)
│
├─ methods ← 可扩展(含本类 + Category 合并的)
├─ properties ← 可扩展
├─ protocols ← 可扩展
├─ firstSubclass / nextSiblingClass
│
└─ ro ───→ class_ro_t(只读,编译期确定)
├─ baseMethodList ← 编译期就有的方法
├─ baseProtocols
├─ ivars ← 成员变量列表
└─ baseProperties
// 编译期确定的只读数据(从 Mach-O __DATA,__objc_data 段直接加载)
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList; // 编译期确定的方法(不含 Category)
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t * baseProperties;
};
// 运行期创建的可读写数据(类首次使用前 realize 时分配)
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro; // 指向只读数据
method_array_t methods; // 可扩展的方法列表(含 Category 添加的)
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
};
class_ro_t:App 启动时从 Mach-O 的__DATA,__objc_data段加载,编译期就确定下来——包括本类直接声明的方法、ivar、属性、协议。只读,不可修改。class_rw_t:类在首次被使用前("realize" 阶段),Runtime 为其分配class_rw_t,将class_ro_t的内容复制/引用过来,然后空出 methods/properties/protocols 供后续 Category 和 Runtime 动态修改合并。- 从 iOS 14 / macOS Big Sur 开始,Apple 引入了
class_rw_ext_t延迟分配优化——对于没有 Category、没有 Runtime 动态修改的类,不需要分配完整的可写方法/属性/协议数组,节省约 30% 的class_rw_t内存。 class_data_bits_t内部利用指针的对齐位做标志位,来区分当前指向的是class_ro_t(realize 之前)还是class_rw_t(realize 之后)。
消息发送完整流程
调用 [receiver message] 时,编译器将其转为:
objc_msgSend(receiver, @selector(message), arg1, arg2, ...);
Runtime 在运行时执行以下查找链:
第一阶段:消息查找
objc_msgSend(receiver, sel)
│
├─ 1. 检测 receiver 是否为 nil
│ → nil 直接返回(OC 向 nil 发消息不会崩溃)
│
├─ 2. 从 receiver->isa 找到类对象
│
├─ 3. 在类对象的 cache_t 中查找缓存
│ → 找到则直接调用 IMP(哈希查找,O(1))
│
├─ 4. 缓存未命中 → 在 class_rw_t 的方法列表中查找
│ → 二分查找(已排序)或遍历
│
├─ 5. 本类未找到 → 沿 superclass 链逐级查找
│ → 每级先查 cache,再查方法列表
│
└─ 6. 到达 nil(根类的 superclass 为 nil)仍未找到
→ 进入第二阶段:消息转发
缓存通过
cache_t实现,采用哈希表结构,以 SEL 为 key,IMP 为 value。每次调用后结果会被缓存,后续调用 O(1)。
第二阶段:消息转发 (Message Forwarding)
未找到实现时,Runtime 给予三次补救机会:
+-----------------------------+
| receiver 无法响应当前消息 |
+-----------------------------+
|
+-----------------+------------------+
| |
↓ ↓
① 动态方法解析 ② 快速转发
resolveInstanceMethod: forwardingTargetForSelector:
resolveClassMethod: 返回一个能处理此消息的对象
可在此用 class_addMethod (最轻量,适合把消息转给备选者)
动态添加实现 |
| |
↓ | (返回 nil 则继续)
② 快速转发 ↓
(若返回 nil) ③ 完整消息转发
| methodSignatureForSelector:
↓ forwardInvocation:
③ 完整消息转发 拿到 NSInvocation 做任意处理
(包裹消息转发、日志、防崩溃)
① 动态方法解析 (Dynamic Method Resolution)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethod)) {
class_addMethod(self, sel, (IMP)dynamicIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
② 备用接收者 (Fast Forwarding)
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(someMethod)) {
return self.backupObject; // 交由备用对象处理
}
return [super forwardingTargetForSelector:aSelector];
}
③ 完整消息转发 (Normal Forwarding)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(someMethod)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([self.backupObject respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self.backupObject];
} else {
// 可以做防崩溃兜底,不要默认调用 doesNotRecognizeSelector:
NSLog(@"[runtime] 未处理的方法: %@", NSStringFromSelector(anInvocation.selector));
}
}
高频追问:消息转发 vs 继承的区别? 继承是静态编译关系,转发是运行时动态决策。转发可以让对象把消息交给"运行时才知道"的目标处理,更灵活但也更难调试。
Category (分类)
底层原理
- 分类的方法在编译时存储在独立的
category_t结构体中。 - 在 Runtime 的
load阶段(_objc_init→map_images→load_images),Runtime 将分类的方法、属性、协议合并到主类的class_rw_t中。 - 分类的方法被添加到类的方法列表前面,因此分类方法会"覆盖"主类方法(实际上是同名前移,主类方法仍在列表中)。
方法覆盖规则
类的方法列表: [mainMethod1, mainMethod2]
分类1的方法: [categoryMethod1]
分类2的方法: [categoryMethod2]
合并后: [categoryMethod2, categoryMethod1, mainMethod1, mainMethod2]
↑
查找时先找到分类方法,"覆盖"了主类方法
- 多个分类都有同名方法 → 最后编译的分类获胜(Build Phases → Compile Sources 中的文件顺序决定)。
- 如果希望调用主类的"原始"方法,可以拿到主类的 IMP 直接调用绕过查找。
具体实现:Category 的方法被合并到 class_rw_t.methods 数组的前面,objc_msgSend 的查找顺序是从头到尾,找到第一个就返回,所以分类的版本"覆盖"了主类。但主类的原始 IMP 仍然在数组后面,没有被删除。先获取所有方法列表
Method *methods = class_copyMethodList(cls, &count);然后通过sel == method_getName(m)去倒序匹配sel。匹配上了method_getImplementation(m)取出末尾的那个原始 IMP 然后通过函数指针调用,就绕过了消息查找的"先到先得"规则。具体实现如下:
// 调用时 — 绕过查找,直接拿到主类的 IMP
IMP getOriginalIMP(Class cls, SEL sel) {
unsigned int count = 0;
Method *methods = class_copyMethodList(cls, &count);
IMP result = NULL;
// 方法列表顺序:[categoryMethod, mainClassMethod]
// index=0 是分类的,index=1 才是主类的
for (unsigned int i = 0; i < count; i++) {
Method m = methods[i];
if (sel == method_getName(m)) {
result = method_getImplementation(m);
// 不 break,继续遍历取最后一个(主类的在最后)
}
}
free(methods);
return result;
}
+load 和 +initialize 的区别
+load | +initialize | |
|---|---|---|
| 调用时机 | App 启动时,类被加载进内存立即调用 | 类第一次收到消息之前 |
| 调用顺序 | 父类 → 子类 → 分类(分类晚于本类) | 父类先初始化,子类未实现则调用父类的 |
| 线程安全 | 主线程串行 | 可能多线程,需要加锁(Runtime 保证只会调用一次,但不保证线程安全) |
| 分类行为 | 每个分类的 +load 都执行 | 分类实现了则覆盖本类的 +initialize |
| 调用方式 | 直接通过函数指针调用(不是消息发送) | 通过 objc_msgSend 调用 |
关联对象 (Associated Objects)
允许在分类中为类添加存储属性(分类不能直接添加成员变量)。
#import <objc/runtime.h>
static const void *kAssociatedKey = &kAssociatedKey;
// setter
- (void)setCustomProperty:(id)value {
objc_setAssociatedObject(self, kAssociatedKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// getter
- (id)customProperty {
return objc_getAssociatedObject(self, kAssociatedKey);
}
关联策略:
| 策略 | 对应 property 语义 |
|---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong, atomic |
OBJC_ASSOCIATION_COPY | copy, atomic |
注意:关联对象在对象释放时由 Runtime 负责清理,但不会像 dealloc 那样及时。另外,关联对象的值会被系统
objc_removeAssociatedObjects清除(不建议手动调用,影响其他关联)。
方法交换 (Method Swizzling)
安全实现模板
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(originalMethod);
SEL swizzledSelector = @selector(swizzledMethod);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 先尝试添加:避免交换父类实现
BOOL didAddMethod = class_addMethod(
class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)
);
if (didAddMethod) {
// 主类没有 originalMethod(从父类继承来的)
// 把 swizzledMethod 的 IMP 换成父类原始的 IMP
class_replaceMethod(
class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod)
);
} else {
// 主类自己有 originalMethod,直接互换
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
常见陷阱
+loadvs+initialize:Swizzle 必须在+load中执行,如果在+initialize中做,可能因为子类未实现+initialize而被父类的 Swizzle 意外影响。dispatch_once:必须保证只执行一次,否则反复交换会回到原始状态。- 线程安全:Swizzle 本身只应在单线程中做一次,但 Swizzle 之后的方法调用应该是线程安全的(取决于具体实现)。
- 命名冲突:分类方法名加前缀(如
track_sendAction:to:forEvent:),降低冲突概率。
高频追问清单
| 问题 | 关键回答要点 |
|---|---|
isa 指针在 ARM64 下如何优化? | non-pointer isa,利用 64 位中的 32+ 位存储引用计数、是否被 weak 引用等信息 |
| 方法缓存如何实现? | cache_t 哈希表,以 SEL 为 key,bucket_t 存储 SEL+IMP,散列后查找 |
objc_msgSend 为什么用汇编写? | 需要支持未知参数数量的跳转、寄存器保护,C 无法做到;同时汇编有最优性能 |
| 消息转发三阶段能举个例子吗? | 第一阶段:动态添加方法(@dynamic);第二阶段:转给代理对象;第三阶段:防崩溃统一处理 |
| Category 能添加成员变量吗? | 不能直接添加(编译期 ivar 偏移已固定),但可以用关联对象实现存储 |
| Swizzle 多个 Category 同时 Swizzle 怎么办? | 顺序取决于 Compile Sources 顺序,后加载的方法优先。建议统一在一个地方管理 Swizzle |
Runtime 的 method 和 sel 区别? | SEL 是方法名注册后的唯一 ID;Method 包含 SEL + IMP + 类型编码的完整结构体 |
load 和 initialize 哪个适合 Swizzle? | load,因为确定且安全,initialize 交换可能导致死循环 |
项目落地版
场景 1:无侵入点击埋点(方法交换)
@interface UIButton (Track)
@end
@implementation UIButton (Track)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:@selector(sendAction:to:forEvent:)
withMethod:@selector(track_sendAction:to:forEvent:)];
});
}
- (void)track_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 埋点上报(可在异步线程、控制采样率)
NSLog(@"[跟踪] 按钮事件: %@ - %@", NSStringFromClass([target class]), NSStringFromSelector(action));
// 调用原始实现
[self track_sendAction:action to:target forEvent:event];
}
@end
场景 2:通用 Swizzle 工具 + 防崩溃封装
// NSObject+SafeSwizzle.h
@interface NSObject (SafeSwizzle)
/// 安全交换实例方法
/// @return 交换是否成功;失败时 error 包含原因
+ (BOOL)swizzleInstanceMethod:(SEL)original withMethod:(SEL)swizzled error:(NSError **)error;
/// 安全交换类方法
+ (BOOL)swizzleClassMethod:(SEL)original withMethod:(SEL)swizzled error:(NSError **)error;
@end
// NSObject+SafeSwizzle.m
@implementation NSObject (SafeSwizzle)
+ (BOOL)swizzleInstanceMethod:(SEL)original withMethod:(SEL)swizzled error:(NSError **)error {
Class cls = [self class];
Method origMethod = class_getInstanceMethod(cls, original);
Method newMethod = class_getInstanceMethod(cls, swizzled);
// 防护1:任一方法不存在,不 crash 而是返回错误
if (!origMethod || !newMethod) {
if (error) {
*error = [NSError errorWithDomain:@"SwizzleError"
code:-1
userInfo:@{NSLocalizedDescriptionKey:
[NSString stringWithFormat:
@"Swizzle 失败: %@/%@ 不存在",
NSStringFromSelector(original),
NSStringFromSelector(swizzled)]}];
}
return NO;
}
// 防护2:class_addMethod 兜底——防止 original 是父类实现导致交换到父类
BOOL didAdd = class_addMethod(cls, original,
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod));
if (didAdd) {
class_replaceMethod(cls, swizzled,
method_getImplementation(origMethod),
method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, newMethod);
}
// 防护3:验证交换后 original 的 IMP 确实是新方法的 IMP
Method checkOrig = class_getInstanceMethod(cls, original);
if (method_getImplementation(checkOrig) == method_getImplementation(origMethod)) {
// 交换未生效——说明 class_addMethod 没走,method_exchangeImplementations 也未修改
// 此时记录日志告警
NSLog(@"[Swizzle Warning] 方法交换疑似未生效: %@", NSStringFromSelector(original));
}
return YES;
}
+ (BOOL)swizzleClassMethod:(SEL)original withMethod:(SEL)swizzled error:(NSError **)error {
Class metaCls = object_getClass((id)[self class]);
Method origMethod = class_getInstanceMethod(metaCls, original);
Method newMethod = class_getInstanceMethod(metaCls, swizzled);
if (!origMethod || !newMethod) {
if (error) {
*error = [NSError errorWithDomain:@"SwizzleError" code:-1
userInfo:@{NSLocalizedDescriptionKey: @"类方法不存在"}];
}
return NO;
}
method_exchangeImplementations(origMethod, newMethod);
return YES;
}
@end
这个方法是工具方法,本身不在任何 +load 里——它由其他分类的 +load 来调用:
// 调用方:某个具体的 Swizzle 场景
@implementation UIViewController (Tracking)
+ (void)load {
NSError *error = nil;
BOOL ok = [UIViewController swizzleInstanceMethod:@selector(viewDidAppear:)
withMethod:@selector(track_viewDidAppear:)
error:&error];
if (!ok) {
// ⚠️ 交换失败,不会 crash,可以告警/降级
NSLog(@"Swizzle 失败: %@", error);
// 生产环境可以上报到 APM,而不是静默崩溃
}
}
它防的是这几种真实 crash 场景:
场景 A:方法名写错了(拼写错误)
// 假设手滑写成 viewDidAppear: → viewDidAppear:(冒号变位置)
[UIViewController swizzleInstanceMethod:@selector(viewDidAppear:)
withMethod:@selector(track_viewDidAppear)
error:&error];
// ↑ 少了冒号,这个方法不存在
- 没防护前:
class_getInstanceMethod返回 NULL,传给method_exchangeImplementations→ EXC_BAD_ACCESS - 有防护:返回 NO,error 里有"track_viewDidAppear 不存在"
场景 B:类没有实现这个方法(从父类继承的)
@interface MyView : UIView // UIView 没有 viewDidAppear:
@end
// 如果有人对 MyView 做 viewDidAppear: 的 Swizzle
// class_addMethod 会成功(因为 MyView 没有),但等价于给 MyView 新增了一个
// 实际场景:Swift 类继承,OC 方法只在父类有
- 没防护前:虽然不 crash,但 IMP 替换行为不符合预期(可能改到了父类)
- 有防护:
class_addMethod正确兜底
场景 C:同一个 Swizzle 被执行了两次(+load 在子类/分类中被重复触发)
// 场景2 的代码还特意返回 BOOL,让调用方可以判断是否重复执行
// 配合 dispatch_once,确保不会发生"交换一次→恢复→再交换→回到原点"的经典问题
所以这个封装的实际价值不是 Swizzle 执行过程中防 crash(Swizzle 本身很少 crash),而是当人为失误(拼写错误、选错类、重复触发)发生时,把 ObjC 的隐式崩溃变成显式的错误返回值,让调用方能感知到问题。
场景 3:Unrecognized Selector 防崩溃
@interface NSObject (CrashGuard)
@end
@implementation NSObject (CrashGuard)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethod:@selector(forwardingTargetForSelector:)
withMethod:@selector(safe_forwardingTargetForSelector:)];
});
}
- (id)safe_forwardingTargetForSelector:(SEL)aSelector {
// 如果是当前类没有的方法,返回一个兜底处理对象
if (![self respondsToSelector:aSelector]) {
// 返回一个临时对象来接收消息,避免崩溃
return [SafeReceiver sharedReceiver];
}
return [self safe_forwardingTargetForSelector:aSelector];
}
@end
场景 4:字典转 Model(KVC + Runtime)
利用 Runtime 获取类的属性列表,遍历并自动赋值:
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [self init]) {
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
NSString *key = [NSString stringWithUTF8String:name];
id value = dict[key];
if (value && value != [NSNull null]) {
// 根据属性类型自动转换(略)
[self setValue:value forKey:key];
}
}
free(properties);
}
return self;
}
场景 5:动态添加方法(用于 @dynamic 声明)
@interface DynamicObject : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation DynamicObject
@dynamic name; // 告诉编译器不做 synthesize
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(setName:)) {
class_addMethod(self, sel, imp_implementationWithBlock(^(id self, NSString *name) {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}), "v@:@");
return YES;
} else if (sel == @selector(name)) {
class_addMethod(self, sel, imp_implementationWithBlock(^(id self) {
return objc_getAssociatedObject(self, _cmd);
}), "@@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
深入学习路径与优先级
初级 (P1) — 理清概念
目标:理解对象、类、元类的关系,能用 Runtime 做基础操作。
- 理解
isa指针的作用和指向关系 - 画一张实例对象、类对象、元类的指向图
- 区分
instanceMethodvsclassMethod的存储位置 - 知道
@selector()/sel_registerName()/NSSelectorFromString()的区别 - 理解 Category 的原理:它能把方法放到哪里?不能放什么?
- 掌握关联对象(Associated Object)的基本使用
- 读 Runtime 源码的关键数据结构定义(
objc_object,objc_class,method_t)
自我检查:
- 能说清楚
[NSObject alloc]的查找过程(通过元类链) - 能用 Runtime 打印一个类的所有属性/方法/协议
中级 (P0) — 掌握消息传递与转发
目标:理解消息发送的全链路,能安全使用方法交换。
- 掌握
objc_msgSend的完整查找流程(缓存 → 本类 → 父类 → 转发) - 手动实现一次消息转发(三个阶段都走一遍)
- 理解
cache_t哈希表结构:查找和插入策略 - 方法交换(Swizzle)的安全模板(含
class_addMethod兜底) - 理解
+load和+initialize的调用时机差异,以及为什么 Swizzle 在+load中做 - 理清
class_ro_tvsclass_rw_t的职责划分 - 了解
swift_allocObject和objc_alloc的关系 - 理解
KVO的 Runtime 实现原理(NSKVONotifying_*中间类 + isa-swizzling)
动手实践:
- 写一个小工具,打印一个 NSObject 子类的完整继承链 + 方法列表
- 实现一个安全 Swizzle 的 Category(上面场景 2 的代码)
- 写一段代码,验证消息转发三阶段(用异常断点配合观察)
自我检查:
- 不用查资料,手写安全 Swizzle 模板
- 能解释:为什么两个 Category Swizzle 同一个方法,行为不可预测?
- 能画图:一个对象收到未知消息后,Runtime 内部走了哪些路径
高级 (P1) — 设计 AOP 与动态架构
目标:能用 Runtime 做架构层面的动态化设计,并控制其风险。
- 设计一个 AOP 框架:基于 Swizzle 的面向切面编程(Hook 任意方法的前后)
- 理解
class_copyIvarList/class_copyPropertyList/class_copyMethodList的底层差异 - 掌握
super关键字的本质:objc_msgSendSuper发送给父类 vs self - 理解
_block invoke与 Block 的 Runtime 结构 - 理解现代 Runtime 的优化:
class_rw_ext_t延迟分配、non-pointer isa - Runtime 与 Swift 的交互:
@objc/@objcMembers/dynamic在 Swift 中的行为 - 了解
NSProxy的实现原理:它是一个不需要继承 NSObject 的纯消息转发类 - 应对面试追问:"Runtime 还能用来做什么?"
实战项目:
- 实现一个方法级别的 AOP 切面库(类似 Aspects),支持 before/after/around 三种模式
- 用
NSProxy实现一个延迟初始化代理或网络请求重试代理 - 为已有的网络库(如 NSURLSession)加上无侵入的请求日志/计时 AOP
自我检查:
- 能设计一套方案,在不动业务代码的前提下给所有 VC 的
viewWillAppear:注入统一的打点 - 能分析 Swizzle 引入的风险,并给出工程层面的治理方案(统一注册 + 白名单 + 测试覆盖)
- 能解释 Apple 为什么在 iOS 14+ 收紧了对
objc_msgSend的调用方式限制
Runtime 相关的系统框架应用
| 框架/机制 | Runtime 角色 |
|---|---|
| KVO | isa-swizzling:运行时创建 NSKVONotifying_XXX 中间子类,重写 setter |
| KVC | class_copyIvarList + objc_msgSend:按 key 路径查找 getter/setter/ivar |
| CoreData | 动态生成 NSManagedObject 子类的存取方法,基于 @dynamic + resolveInstanceMethod: |
| NSCoding / NSSecureCoding | class_copyIvarList 实现自动归档/解档 |
| UIStoryboard / XIB | initWithCoder: + awakeFromNib 由 Runtime 驱动对象重建 |
| NSDictionary / NSArray 桥接 | Toll-free bridging 依赖 isa 指针的运行时转换 |
| Swift 的 @objc 动态派发 | Swift 类标记 @objcMembers 后,会生成 OC 兼容的 vtable 和消息派发表 |