将来的自己,会感谢现在努力的自己!

0%

iOS底层探索系列--方法的本质

Objective‑C 方法的本质

OC 中的所有方法调用,最终都会转换为 C 函数 objc_msgSend 的调用,对象的 isa、类的 bits、方法缓存、继承链共同为消息查找提供支持。若查找失败,Runtime 还会通过动态方法解析、消息转发提供三次补救机会,防止程序崩溃。

一、数据结构基础:方法在内存中的表现

1.1 objc_class 的核心结构

每个 OC 类在底层被表示为一个 objc_class 结构体:

1
2
3
4
5
struct objc_class : objc_object {
Class superclass; // 父类指针
cache_t cache; // 方法缓存(哈希表)
class_data_bits_t bits; // 存储类信息(方法、属性、协议等)
};
  • isa:继承自 objc_object,指向类对象或元类对象。
  • superclass:指向父类。
  • cache:缓存最近调用的方法,用于加速消息发送。
  • bits:压缩存储了类的大部分元数据,通过 bits.data() 获取 class_rw_t

1.2 class_rw_t 与 class_ro_t

bits 通过位运算解压后指向 class_rw_t(read‑write,可读写),存储运行时可以动态修改的信息:

1
2
3
4
5
6
struct class_rw_t {
const class_ro_t *ro; // 只读信息,编译时确定
method_array_t methods; // 方法列表
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表
};

class_ro_t(read‑only,只读)存储编译时确定的类元数据:

1
2
3
4
5
6
7
8
9
10
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // 实例变量占用的内存大小
const char *name; // 类名
method_list_t *baseMethods; // 编译时确定的方法列表
ivar_list_t *ivars; // 成员变量列表
property_list_t *baseProperties; // 属性列表
protocol_list_t *baseProtocols; // 协议列表
};

为什么需要两份结构?class_ro_t 存储编译期已确定的只读信息,在内存紧张时可被清除,需要时从磁盘重新加载;class_rw_t 存储运行时动态添加的内容(如 Category 方法),必须常驻内存。Category 的方法会被合并到 class_rw_tmethods 数组中,且插入在原类方法列表之前,这解释了为什么 Category 方法可以“覆盖”原类方法。

1.3 method_t:方法的底层表示

每个方法在底层被封装为一个 method_t 结构体:

1
2
3
4
5
struct method_t {
SEL name; // 方法选择器(方法名)
const char *types; // 类型编码(返回值类型、参数类型)
IMP imp; // 函数指针,指向方法的实现地址
};

SEL(选择器):方法名的唯一标识符,在编译期生成(也可动态注册),本质是一个 C 字符串。不同类中同名方法对应的 SEL 相同。

IMP(函数指针):指向方法实现代码的内存地址,定义如下:

1
typedef id (*IMP)(id, SEL, ...);

所有 OC 方法本质上是至少携带 self_cmd 两个隐式参数的 C 函数,因此可以直接通过 IMP 调用,绕过消息发送机制以提高效率。

types(类型编码):描述方法返回值类型和参数类型的字符串,用于 Runtime 在消息转发中判断参数匹配。例如 "v@:" 表示 void 返回值、id 类型 selfSEL 类型 _cmd。可通过 @encode 指令获取任意类型的编码字符串。

二、消息发送的快速路径:汇编实现的缓存查找

2.1 objc_msgSend 的汇编实现

objc_msgSend 是整个消息发送机制的核心入口。之所以用汇编实现,主要有两个原因:

  1. C 语言函数无法保留未知个数的参数并跳转到任意函数指针。
  2. 方法调用极为频繁,汇编能最大化执行效率。

ARM64 架构下,objc_msgSend 的前两个参数固定:

  • x0 寄存器:消息接收者 self
  • x1 寄存器:方法选择器 _cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
ENTRY _objc_msgSend

// Step 1: 检查消息接收者
cmp x0, #0
b.le LNilOrTagged // 为 nil 或 Tagged Pointer 则走特殊分支

// Step 2: 通过 isa 获取类对象
ldr x13, [x0] // x13 = obj->isa
and x16, x13, #ISA_MASK // x16 = class

LGetIsaDone:
// Step 3: 进入缓存查找
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

isa 在 arm64 下并非直接指向类,而是包含了引用计数等额外信息的联合体。#ISA_MASK 掩码用于从 isa 中提取真正的类地址,ISA_MASK 真机值为 0x0000000ffffffff8ULL

2.2 CacheLookup:缓存查找

CacheLookup 负责在类的 cache_t 哈希表中快速查找 IMP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.macro CacheLookup

// 获取类的 cache_t
mov x15, x16 // x15 = class
ldr p11, [x16, #CACHE] // p11 = class->cache

// 获取 bucket 数组和 mask
ldr p10, [x11, #BUCKETS] // p10 = buckets (哈希表)
and w12, w1, w11 // w12 = _cmd & mask,计算哈希下标
add p13, p10, p12, LSL #4 // p13 = buckets + index * 16 (bucket 大小16字节)

1: // 循环查找
ldp p9, p17, [p13] // p9 = bucket->sel, p17 = bucket->imp
cmp p9, p1 // 比较 sel 与 _cmd
b.eq 2f // 命中,跳转执行
cbz p9, 3f // sel 为 0,未命中
sub p13, p13, #16 // 向前移动一个 bucket
b 1b // 继续循环

2: // 命中,跳转到 IMP
br p17

3: // 未命中,调用慢速查找
TailCallCachedImp x17, x15, __objc_msgSend_uncached

.endmacro
  • 哈希算法index = sel & mask,采用开放地址法解决哈希冲突,冲突时向前探测(index - 1,倒序存储)。
  • bucket 结构:arm64 下 bucket 中先存 IMP 再存 SEL,每个 bucket 大小 16 字节。

2.3 cache_t 与 bucket_t

每个类的 cache_t 是增量扩展的哈希表,缓存最近调用的方法:

1
2
3
4
5
6
7
8
9
10
struct cache_t {
bucket_t *_buckets; // 哈希表数组
mask_t _mask; // 哈希表长度 - 1
mask_t _occupied; // 已占用的 bucket 数量
};

struct bucket_t {
atomic<IMP> _imp; // 方法实现指针
atomic<SEL> _sel; // 方法选择器
};
  • 缓存策略:方法始终先查缓存→查方法列表(慢速)→找到后插入缓存。扩容时机为 _occupied > maxOccupied()(arm64 下容量阈值是 mask 的 7/8),容量翻倍。
  • 线程安全:缓存读取采用无锁设计;插入和清空利用 cacheUpdateLock 机制,并通过延迟释放(Garbage List)避免正在读取缓存的线程访问已释放内存。

2.4 Tagged Pointer 与 nil 处理

汇编入口处对特殊情况的快速处理:

  • receiver 为 nil:向 nil 发送消息在 OC 中是合法操作(静默返回 nil/0),此处直接返回,不会引发崩溃。
  • Tagged Pointer:指针对小对象的一种优化,当指针值的最高位为 1 时,指针本身直接存储值。Runtime 需在 _objc_debug_taggedpointer_classes 表中查类型。

三、消息发送的慢速路径:方法列表遍历与继承链查找

当缓存未命中时,__objc_msgSend_uncached 会调用 _lookUpImpOrForward 进行慢速查找:

1
2
3
4
STATIC_ENTRY __objc_msgSend_uncached
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached

MethodTableLookup 将调用 C 函数 _lookUpImpOrForward(后续重命名为 lookUpImpOrForward),返回的 IMP 存入 x17

3.1 lookUpImpOrForward 核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
IMP imp = nil;
Class curClass = cls;

for (unsigned attempts = unreasonableClassCount();;) {
// 在当前类的方法列表中查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}

// 如果当前类无该方法,则递归查找父类
if ((curClass = curClass->getSuperclass()) == nil) {
imp = forward_imp;
break;
}

// 在父类的缓存中查找
imp = cache_getImp(curClass, sel);
if (imp && imp != forward_imp) {
goto done;
}
}

// 仍未找到,进入动态方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
imp = resolveMethod_locked(inst, sel, cls, behavior);
}

done:
// 找到 IMP 后,将其存储到当前类的缓存中
log_and_fill_cache(cls, imp, sel, inst, curClass);
return imp;
}

关键细节

  1. 方法列表查找:已排序列表用二分查找提高效率,未排序则线性遍历。
  2. 父类查找优先级:先查父类缓存→再查父类方法列表,每查到一个方法就立即跳转到 done 将 IMP 存入当前类的缓存中。这样下次调用时可直接命中当前类的缓存,无须重复遍历继承链。
  3. forward_imp:若找到根类仍无结果,imp 被设为 _objc_msgForward_impcache(消息转发的入口标记)。

3.2 Category 方法与类方法存储

  • 实例方法:存储在类对象的方法列表中。
  • 类方法:存储在元类对象的方法列表中。+initialize+load 也在元类中,查找 [ClassName classMethod] 时,Runtime 通过实例对象的 isa→类对象、类对象的 isa→元类对象 逐层查找。

四、动态方法决议

lookUpImpOrForward 中,若遍历完继承链仍未找到 IMP,将调用 resolveMethod_locked 进入第一次补救机会:

1
2
3
4
5
6
7
8
9
10
11
12
13
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {
if (!cls->isMetaClass()) {
// 实例方法:调用 resolveInstanceMethod
resolveInstanceMethod(inst, sel, cls);
} else {
// 类方法:调用 resolveClassMethod
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
// 若元类没有,再尝试实例方法版本(NSObject 兜底)
resolveInstanceMethod(inst, sel, cls);
}
}
}

此阶段可动态为 sel 添加一个 IMP 实现

1
2
3
4
5
6
7
8
9
10
11
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo)) {
class_addMethod(self, sel, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}

void fooMethod(id self, SEL _cmd) {
NSLog(@"动态添加的方法被执行");
}
  • resolveInstanceMethod 在慢速查找阶段会被调用两次:第一次供开发者动态添加实现;添加后 Runtime 会重新执行一次查找流程——该方法会被存入缓存,下次调用直接命中快速路径。若返回 NO 且仍未找到,进入消息转发阶段。
  • 类方法的决议由 resolveClassMethod 处理,内部查找发生在元类对象上。若元类未找到,Runtime 还会再调用实例方法版本作为最后的兜底尝试。

五、消息转发

若动态方法决议仍未找到 IMP,进入消息转发阶段,Runtime 再提供两次机会重新定位消息接收者或处理方法调用:

5.1 快速转发:forwardingTargetForSelector

1
2
3
4
5
6
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
return [OtherObject new]; // 转发给其他对象
}
return [super forwardingTargetForSelector:aSelector];
}

forwardingTargetForSelector 返回一个能响应 aSelector 的对象。此方法代价最低,不涉及 NSInvocation 对象创建。返回 nil 则进入下一阶段。

5.2 慢速转发:methodSignatureForSelector + forwardInvocation

标准转发的完整两步流程为:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Step 1:获取方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}

// Step 2:核心转发逻辑
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 可修改目标对象、参数,或静默忽略
[anInvocation invokeWithTarget:otherObject];
}

methodSignatureForSelector 返回 nil 时会直接触发 doesNotRecognizeSelector: 并崩溃。此阶段允许在运行时动态决定如何响应方法调用,是 AOP 和防 Crash 等高级工程实现的基础,也是代价最高的一步。

六、完整流程图与总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[objc_msgSend]

├─── receiver == nil ? ──→ 返回 nil (静默)

├─── Tagged Pointer ? ──→ 特殊处理


快速路径:cache 查找

├─── hit ──→ 跳转到 IMP


慢速路径:lookUpImpOrForward

├─── 当前类 methodList 查找
├─── 父类递归查找(cache→methodList)

├─── 找到 ──→ 填充到 cache ──→ 跳转到 IMP


动态方法决议

├─── resolveInstanceMethod / resolveClassMethod
├─── class_addMethod 动态添加 IMP
├─── 添加成功 ──→ 重走查找流程


消息转发

├─── forwardingTargetForSelector(快速转发)
├─── methodSignatureForSelector + forwardInvocation(慢速转发)


doesNotRecognizeSelector:抛出异常,程序 crash

关键知识点回顾

阶段 核心函数 主要工作 次数
快速查找 objc_msgSend(汇编) 缓存哈希表查找,命中即返回 每次调用
慢速查找 lookUpImpOrForward 遍历方法列表 + 继承链 仅首次或缓存失效
动态决议 resolveInstanceMethod 动态添加 IMP 实现 仅一次
快速转发 forwardingTargetForSelector 更换消息接收者 仅一次
慢速转发 forwardInvocation 完整控制 NSInvocation 仅一次
彻底失败 doesNotRecognizeSelector 抛出异常 最终手段

七、扩展话题

7.1 Tagged Pointer

Tagged Pointer 是一种不分配堆内存、直接将值存储在指针中的优化手段。当指针最高位为 1 时,Runtime 识别其为 Tagged Pointer。

判断与类获取

  • arm64:第 60-63 位作为 tag 索引,通过 _objc_debug_taggedpointer_classes 表获取对应的类。
  • 方法调用:Tagged Pointer 对象的 isa 并非真实指针,汇编路径会进入 LNilOrTagged 分支,从预定义表中取类,之后走和普通对象相同的 CacheLookup 流程。

7.2 类方法(☕️)的特殊性

若类方法查找失败且元类未处理,Runtime 会调用实例方法的 resolve 作为最后的兜底,本质上是因为 NSObject 的元类继承自 NSObject 类本身(根元类的父类是根类)。

1
2
3
4
5
6
7
8
9
10
+ (BOOL)resolveClassMethod:(SEL)sel { /* 元类中查找失败,return NO */ }
// Runtime 继续调用以下方法(实例版本)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(classMethod)) {
// 注意:此处添加的是实例方法,但仍可作为类方法的兜底实现
class_addMethod(self, sel, (IMP)classMethodImp, "v@:");
return YES;
}
return NO;
}

7.3 方法交换(Method Swizzling)

本质是修改 method_t 中的 IMP 指针,利用 OC 的运行时动态绑定特性,可在运行时修改方法实现。结合 resolveInstanceMethodclass_addMethod,可在运行时完成方法的动态注入与替换,是 AOP 与热修复(JSPatch 等)实现的基础。

八、附属代码

有下面一份代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <Foundation/Foundation.h>
#import "FLYPerson.h"

static void run() {

NSLog(@"%s", __func__);
}

int main(int argc, char * argv[]) {
@autoreleasepool {

FLYPerson * person = [FLYPerson alloc];
[person walk];
[FLYPerson say];
run();
}
return 0;
}

用clang编译之后,提取我们想要的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void run() {

NSLog((NSString *)&__NSConstantStringImpl__var_folders_dq_mwrk2yjx1b18hws5lc3pb7g80000gn_T_main_11652b_mi_0, __func__);
}

int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

FLYPerson * person = ((FLYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FLYPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("walk"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FLYPerson"), sel_registerName("say"));
run();
}
return 0;
}

一、runtime

定义 : 一套由c、c++和汇编编写能够为oc提供运行时功能的api。
runtime有两个版本,分别是modern’ 和 ‘legacy’, legacy是老版本中的runtime,Objective-C 2.0 以及后续版本中用的都是modern版本的runtime(参考官方文档)。

使用方式可以归纳为三种:
1、Objective-C code @selector()
2、NSObject的方法 NSSelectorFromString()
3、sel_registerName 函数api

一、objc_msgSend

首先,objc_msgSend是用汇编写的,那么为什么会选用汇编写呢?原因有如下两点

  • 1、因为在C语言中不可能通过写一个函数来保留未知的参数并且跳转到一个任意的函数指针。C语言没有足够做这件事的必要特性。
  • 2、因为objc_msgSend执行的频率是想当非常之高,所以必须保证执行的足够快,这里汇编也就必然是首选。

objc_msgSend可以分为快速路径,慢速路径,都是用C实现的。

作用流程:
1:ENTRY _objc_msgSend
2: 对消息接受者(id self, sel _cmd) 判断处理
3:taggedPointer判断处理
4:‘GetClassFromlsa_p16 isa’ 指针处理 - class
5:CacheLoopup 查找缓存
6:‘cache_t’ 处理 ’bucket’ 以及内存哈希处理
6.1: 找不到递归下一个 ‘bucket’
6.2: 找到了就返回 ‘{imp, sel} = *bucket -> imp’
6.3: 遇到意外就重试
6.4: 找不到就 ‘JumpMiss’
7:__objc_msgSend_uncached 告诉找不到缓存 ‘imp’
8:‘STATIC_ENTRY __objc_msgSend_uncached’
9:‘MethodTableLookup’ 方法表查找
9.1: ‘sava parameter registers’
9.2: ‘self’ 以及 _cmd 准备
9.3: ‘_class_lookupMethodAndLoadCache3’ 调用

下面展示两种用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(int argc, char * argv[]) {
@autoreleasepool {

FLYStudent * person = [FLYStudent alloc];

objc_msgSend(person, sel_registerName("walk"));

//调用对象方法
struct objc_super flySuper;
flySuper.receiver = person;
flySuper.super_class = [person class];//receiver的isa
objc_msgSendSuper(&flySuper, sel_registerName("walk"));

//调用类方法
struct objc_super classSuper;
classSuper.receiver = [person class];
classSuper.super_class = object_getClass([person class]);//receiver的isa(或者为receiver的isa的superClass)
objc_msgSendSuper(&classSuper, sel_registerName("sayXiXi"));
}
return 0;
}
1
2
3
4
5
6

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;

runtimeLock.assertUnlocked();

// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}

// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.

// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.

runtimeLock.lock();
checkIsKnownClass(cls);//检查是否是已知类

/**
如果没有初始化,去初始化该类
*/
if (!cls->isRealized()) {
realizeClass(cls);
}

/**
initialize该类
*/
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}


retry:
runtimeLock.assertLocked();

// Try this class's cache.
/**
取缓存,以下为汇编源码,防止被hook,以及运行速度和性能
STATIC_ENTRY _cache_getImp

GetClassFromIsa_p16 p0
CacheLookup GETIMP

LGetImpMiss:
mov p0, #0
ret

END_ENTRY _cache_getImp
*/
imp = cache_getImp(cls, sel);
if (imp) goto done;

/**
查找当前类里面方法列表
*/
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}

/**
查找父类里面方法
*/
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}

// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}

// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}


/**
消息转发机制
*/
// No implementation found. Try method resolver once.

if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}

// No implementation found, and method resolver didn't help.
// Use forwarding.

/**
最终都没有找到,走_objc_msgForward_impcache方法,苹果留的最后的后门,
汇编源码:
STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b __objc_msgForward

END_ENTRY __objc_msgForward_impcache

ENTRY __objc_msgForward

adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17

END_ENTRY __objc_msgForward

以上__objc_forward_handler是非汇编
*/
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

done:
runtimeLock.unlock();

return imp;
}
1
2
3
4
5
6
7
8
9
__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]

_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());

if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);

// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}