前言
方法查找 , 动态方法解析以及消息转发已经是面试常客了 , 也是我们了解 Aspects 或者饿了么的 AOP
- Stinger 等等优秀的三方库必不可少的基础知识 .
上篇文章 手把手带你探索OC方法的本质 我们讲到 objc_msgSend
函数汇编查找方法缓存的流程 , 直到缓存没有命中 , 由 JumpMiss
调用了 bl __class_lookupMethodAndLoadCache3
, 由此找到了 lookUpImpOrForward
回到了 c 函数中 , 开启方法查找与消息转发的流程 .
- 建议 :
开启本篇文章探索前 , 请对 OC类对象/实例对象/元类 各自存储着什么内容 , 以及 isa 的走位有详细了解 .
1、方法查找流程
继续 上篇文章 , 我们 objc_msgSend
汇编查找缓存 , 缓存 miss
时来到 _class_lookupMethodAndLoadCache3
这个函数 ,
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
/*
cls:如果是实例方法那就是类,如果是类方法那就是元类
sel:方法名
obj:方法调用者
*/
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
首先需要明白的是 :
- 当调用实例对象方法时 , 查找的将是类对象 .
- 当调用类方法是 , 查找的将是元类对象 .
- 注意 : 本文下面书内容中所指 "本类" 基于此前提 . 就是说当调用实例方法 , 本类就是指类对象 , 当调用类方法 , 本类就是指元类 .
lookUpImpOrForward
注意 : runtime
有两个版本 , 找源码的时候不要找到 objc-class-old.mm 中去了 .

1.1 - 概览
源码如下 :
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
//如果需要从缓存里面查找,那需要先从缓存里面找
// 第一次进入为 false , 因为汇编快速查找流程没找到进入.
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp; // 找到就直接返回 imp
}
// 加把锁
runtimeLock.lock();
checkIsKnownClass(cls);
/** - 为查找方法做准备条件,判断类有没有加载好
- 如果没有加载好,那就先加载一下类信息,准备好父类、元类
- 只会加载一次.
- 具体可以参考 realizeClass 具体实现.
*/
if (!cls->isRealized()) {
realizeClass(cls);
}
// 当 sel == initialize, _class_initialize 将会调用 +initialize
// 确保对象已经初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
retry:
/** 这部分代码过长先省略 , 后面详细分析...*/
done:
runtimeLock.unlock();
return imp;
}
具体操作都加了注释.
- 先来看下调用普通实例方法时 , 该方法的参数情况 .
第一次进入该函数是由
objc_msgSend
汇编快速查找完cache
, 然后cache miss
才会来到 , 因此传入cache
为false
.
- 源码分析 :
- 1️⃣ : 如果指定需要查找缓存 , 就先查一次缓存 , 找到直接
return
, 找不到继续下面步骤 ( 第一次进来为不查找缓存 , 因为没有缓存 ) . - 2️⃣ : 加锁 , 确保类已经初始化 ,准备好父类 , 元类 .
- 1️⃣ : 如果指定需要查找缓存 , 就先查一次缓存 , 找到直接
1.2 - retry
1.2.1 - 查找本类
// 查找本类缓存
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 查找本类方法列表 class_data_bits_t -> bits.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
- 源码分析 :
- 1️⃣ : 查找本类缓存 , 找到直接跳转到
done
, 找不到继续下面步骤. - 2️⃣ : 查找本类方法列表
class_data_bits_t
中 , 找到后进行缓存并打印 , 直接跳转到done
, 找不到继续下面步骤 .
- 1️⃣ : 查找本类缓存 , 找到直接跳转到
1.2.2 - 循环查找父类
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 查找父类缓存
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 找到 -> 把方法缓存,跳转到 done
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// 查找父类方法列表
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
- 源码分析 :
- 1️⃣ : 循环遍历父类 , 查找方法缓存 , 找到直接跳转到
done
, 找不到继续下面步骤. - 2️⃣ : 查找方法列表 , 找到把方法添加到缓存并打印 , 直接跳转到
done
, 找不到继续遍历 . - 3️⃣ : 直到遍历结束 superclass 为
nil
, 继续下面流程 .
- 1️⃣ : 循环遍历父类 , 查找方法缓存 , 找到直接跳转到
1.3 动态方法解析
retry
部分经过 本类 , 父类方法缓存和方法列表查找都没找到时来到此处. 代码如下:
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;
}
我们可以看到,在经过
_class_resolveMethod
后,会进行一遍retry
操作,重新进行一遍方法的查找流程,并且只有一次动态方法解析的机会 .
官方给出注释如下 :
Don't cache the result ; we don't hold the lock so it may have changed already. Re-do the search from scratch instead .
我们先来看下 _class_resolveMethod
的实现 , 再来看这个问题 .
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);
}
}
}
源码流程分析 :
如果本类为元类 : 说明方法为类方法 . 调用
_class_resolveInstanceMethod
如果本类不是元类 : 说明方法为实例方法 . 调用
_class_resolveClassMethod
- 调用
lookUpImpOrNil
如果没找到 , 调用 _class_resolveInstanceMethod
1.3.1 _class_resolveInstanceMethod
源码如下 :
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
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 {
_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));
}
}
}
源码流程分析 :
- 判断是否实现
SEL_resolveInstanceMethod
方法,即 +(BOOL)resolveInstanceMethod:(SEL)sel , 提示 ,NSObject
已经实现这个方法 , 默认返回为NO
- 向本类发送
SEL_resolveInstanceMethod
消息 , 即调用这个方法 . - 调用解析器方法 ( SEL_resolveInstanceMethod ) 完成后 , 重新检查有没有这个
sel
的imp
. - 该方法执行完 , 回归到
lookUpImpOrForward
中 , 进行retry
,triedResolver
设置为YES
.
讲到这里 , 是不是就对我们刚刚所说的 : 在经过 _class_resolveMethod
后,会进行一遍 retry
操作,重新进行一遍方法的查找流程,并且只有一次动态方法解析的机会 豁然开朗了 .
1.3.2 _class_resolveClassMethod
这个方法和 1.3.1
- _class_resolveInstanceMethod
基本一样 , 只不过发消息的对象变成元类 , 如下图. 这里就不重复赘述了 .

1.4 消息转发入口
retry
最后一步中源码如下 :
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
在 lookUpImpOrForward
方法中,经过对本类 , 父类的缓存,方法列表查找和动态方法解析后,如果以上步骤都没有进行处理,那么就会进入,消息处理的最后一步,即消息转发流程,这也是苹果预留的补救方法崩溃最后一步。
1.5 done
找到 imp , 返回.
done:
runtimeLock.unlock();
return imp;
问题
为什么当调用类方法时 , 对元类进行了动态方法解析也没有找到 imp
时 , 还要再对类进行实例方法的解析呢 ? 如下图 :

答 :
其实这是由于 isa 走位的原因 , 根源类的父类是 NSObject ,
类方法和实例方法的区别是存储地不同 , 类方法是在元类中 , 实例方法是在类对象中 , 都是存储在方法列表里 , 并无
+
/-
之分 .因此当调用类方法时 , 继承链走到根源类并没有查找到时 , 是需要继续走其父类
NSObject
查一次动态方法解析器的 , 而 NSObject 作为类对象 , 是需要通过实例方法去查找的 .
总结 :
也就是说 , 当在本类和父类中方法缓存以及方法列表都没有找到 imp
时 , 苹果给了我们一次动态方法解析的机会 , 也就是在 resolveInstanceMethod
中自己给这个方法添加 imp
, return true
即可 .
2、消息转发
在上面流程中 , 都没有找到函数实现地址 , 那么就进入了消息转发流程 , 调用入口如下 :

_objc_msgForward_impcache
同 objc_msgSend
一样 , 是由汇编实现的 , 因此全局搜索 , 直接来到 入口处.

2.1 _objc_msgForward_impcache
代码如下 :
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
还记得上篇文章提到的 汇编函数入口和结束符吧 ENTRY
, END_ENTRY
.
-
这段函数很明显 只调用了
__objc_msgForward
. -
搜索
__objc_msgForward
, 就在下面 , 同样是汇编函数 .
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
放到x17
寄存器中 . - 从
x17
寄存器中将低32
位的数据放到p17
里 . - 调用
x17
存储的函数 imp .
也就是说我们需要找 _objc_forward_handler
.
2.2 _objc_forward_handler
全局搜索 , 在 objc-runtime.mm
中找到如下 :
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
再搜索 objc_defaultForwardHandler
, 找不到啥时候赋值的了 , 跟不进去了 ..

怎么办呢 ?
别急 , 既然前辈们都已经探索出来了 , 必然是有地方可以寻找点蛛丝马迹的 . 回顾一下本篇文章方法查找流程里 , 我们忽视掉一些细节 .

在整个流程里 , 都是有一个打印的. 那么打印到哪儿了呢 ? 点进去看下 .
2.3 打印查找
2.3.1 log_and_fill_cache
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
其中 SUPPORT_MESSAGE_LOGGING
为 :
# define SUPPORT_MESSAGE_LOGGING 1
也就是说 objcMsgLogEnabled 只要为 true , 就可以打印了 , 那么怎么把它设置为 true 呢 ?
2.3.2 instrumentObjcMessageSends
cmd
+ 点击 objcMsgLogEnabled
. 来到 objc-class.mm
. 搜索 objcMsgLogEnabled
, 我们看到有如下函数 .
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
这里我们找到 当调用这个函数 , 参数会被赋值给 objcMsgLogEnabled
.
因此来到我们的代码中 .
修改代码如下 :
#import "LBStudent.h"
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LBStudent *student = [[LBStudent alloc] init];
instrumentObjcMessageSends(true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
运行工程 . 来到 /private/tmp
目录下 , 打开最新的一份 msgSends
文件 :

打开如下 :

2.4 快速转发流程
这其中 resolveInstanceMethod
动态方法解析我们已经分析过了 , 来到 forwardingTargetForSelector
, 搜索一下发现其只有在 NSObject
中默认实现了返回为 nil
, 并无其他的方法实现了 .
2.4.1 查找源码
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
2.4.2 官方文档
那么这时该怎么办 ? 不急 , 来找官方文档 .



小提示 :
主要看 Discussion
2.4.3 方法解析
文档说明了该方法的前世今生 , 总结如下 :
- 1️⃣ : 该方法的目的 , 说白了就是你搞不定的方法 , 就交给别人处理 . 但是不能返回
self
,否则会一直找不到 , 陷入死循环 .- 2️⃣ : 如果不实现或者返回
nil
,会走到forwardInvocation
: 方法进行处理 .- 3️⃣ : 被转发消息的接受者参数和返回值等需要和原方法相同 .
也就是说我们可以自己新定义一个类 , 或者选一个现有的类 , 其实现了这个方法 , 就交由其去处理.
2.4.4 方法使用
写法如下 :
// 消息转发流程
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(saySomething)) {
return [LBTeacher new];
}
return [super forwardingTargetForSelector:aSelector];
}
在运行 , 就很愉快的打印出 LBTeacher
的 saySomething
方法了 .
这个转发给单一方法实现对象的过程 , 我们也称之为 快速消息转发流程 .
2.5 慢速转发流程
当快速消息转发流程也并没有实现 , 或者返回为 nil
, 就来到了 慢速消息转发流程
.
同样 , 根据我们的打印来寻求线索 .

2.5.1 查找源码
找到 methodSignatureForSelector
.
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject instanceMethodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
查看源码同样是没有发现 . 老办法 , 找官方文档 .
2.5.2 官方文档

2.5.3 方法解析
其实熟悉方法和函数的同学应该很清楚 , 这个就是我们的方法签名 .
- 该方法是让我们根据方法选择器
SEL
生成一个NSMethodSignature
方法签名并返回 .- 这个方法签名里面其实就是封装了返回值类型,参数类型等信息。
2.5.4 方法使用
写法如下 :
// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(saySomething)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
2.5.5 小提示
Code | Meaning |
---|---|
c | A char |
i | An int |
s | A short |
l | A long |
l | is treated as a 32-bit quantity on 64-bit programs. |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
很明显 , 只给苹果一个方法签名是肯定不行的 , 必须要有方法实现啊 , 别急 , 刚刚我们在 forwardingTargetForSelector
的官方文档中看到 , 如果返回 nil
或者不实现 , 则会进入 forwardInvocation:
的流程 .

2.5.6 forwardInvocation
同样查找官方文档后 , 方法解析如下 :
-
1️⃣ :
forwardInvocation
和methodSignatureForSelector
必须是同时存在的 . -
2️⃣ : 该方法可以自由指派多个对象来接收这个消息 .
-
3️⃣ : 将消息发送到该对象。保存结果 ,运行时系统将提取此结果并将其传递给原始发消息人。
该方法可以指定多个转发者 , 而且由于 NSInvocation
的封装 , 可以自由调配 target
, 参数等等 , 自由度较高 , 但与此同时花费也将更高 , 官方文档称其为 more expensive forwardInvocation: machinery .
使用方法如下 :
// 消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s",__func__);
NSLog(@"%@",anInvocation);
SEL aSelector = [anInvocation selector];
if ([self respondsToSelector:aSelector]){
[anInvocation invoke];
}else if ([[LBTeacher new] respondsToSelector:aSelector]){
[anInvocation invokeWithTarget:[LBTeacher new]];
}
else
[super forwardInvocation:anInvocation];
}
至此 , 整个方法查找 , 动态方法解析 , 消息转发的完整流程我们已经讲完了 , 整个流程走完 , 如果仍然没有找到方法实现 , 那么苹果表示 我给过你太多机会了 , 我也没辙了.
3、面试题
最后 留下两道面试题 , 以帮助大家检验自己是否理解了 方法的本质 , 以及 方法查找和消息转发的知识 .
3.1 isKindOfClass 与 isMemberOfClass
#import "LBPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[LBPerson class] isKindOfClass:[LBPerson class]];
BOOL re4 = [(id)[LBPerson class] isMemberOfClass:[LBPerson class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LBPerson alloc] isKindOfClass:[LBPerson class]];
BOOL re8 = [(id)[LBPerson alloc] isMemberOfClass:[LBPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
LBPerson 为一个普通的
OC
类 .
问打印结果.
3.2 方法查找面试题
3.2.1 题目 1 :
LBStudent
继承自 LBPerson
, LBPerson
中有一个方法如下 :
//声明
- (void)sayHi;
//实现
- (void)sayHi{
NSLog(@"hi");
}
问
[LBStudent performSelector:@selector(sayHi)];
调用结果是什么 .
3.2.2 题目 2 :
LBPerson
继承自 NSObject
, LBStudent
继承于 LBPerson
, 项目中有一个 NSObject
的分类如下 :
// NSObject+LB.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (LB)
- (void)sayHi;
@end
NS_ASSUME_NONNULL_END
// NSObject+LB.m
#import "NSObject+LG.h"
#import <AppKit/AppKit.h>
@implementation NSObject (LB)
- (void)sayHi{
NSLog(@"%s",__func__);
}
@end
问 :
[LBStudent performSelector:@selector(sayHi)];
打印结果是什么 .
如果将分类里 类方法改为实例方法 , 打印结果是什么 ?
大家可以在评论区留下自己的答案 , 下期揭晓 .
4、发散思维
大家可以由此来开拓思维 , 想一想如果让你来做一个防止崩溃的三方库 , 你会如何思考 , 如何设计 ?