建议先看下
Objc4-818底层探索(九):msgSend消息发送(二): 慢速查找
名词解释:
-
sel
: 方法编号, 可以理解成一本书的目录, 通过目录可以找到页码(imp) -
imp
: 函数指针, 可以理解成数的页面, 根据页面可找到内容(函数实现) -
resolve
: 决定, 决议 -
instance
: 实例 -
method
: 方法
forward_imp
先看例子:
通常我们.h
只写方法声明, .m
不写方法实现 或者 利用函数performSelector:@selector(XXX)
, 但是都没有实现方法, 就会报方法找不到错误unrecognized selector sent to instance XXX
其实这个错误来自于lookUpImpOrForward
中的 forward_imp
方法
const IMP forward_imp = (IMP)_objc_msgForward_impcache; // 汇编方法
全局搜索objc_msgForward_impcache
, 这个方法在汇编里面
关于下划线知识点
1、C/C++
调用汇编
: 查找汇编时,C/C++
调用的方法需要多加一个下划线( _
→ __
)
2、汇编
调用 C/C++
方法: 查找C/C++
方法,需要将汇编调用的方法去掉一个下划线(__
→ _
)
__objc_msgForward_impcache
里面调用__objc_msgForward
方法进入__objc_msgForward
看下源码
ENTRY __objc_msgForward // 进入 __objc_msgForward
adrp x17, __objc_forward_handler@PAGE // 调用__objc_forward_handler方法
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17 // 返回函数指针
END_ENTRY __objc_msgForward // 结束 __objc_msgForward
其中
-
adrp
: 以页@PAGE
(也有写P
的)为单位的大范围的地址读取指令
通俗讲为: ADRP
指令可以理解成先进行PC + imm
(偏移值)然后找到lable所在的一个4KB
的页,然后取得label
的基址,再进行偏移去寻址
这里就是 adrp指令
计算出这个__objc_forward_handler@PAGE
(偏移量), 之后将基址存到寄存器X17
中, 关键就是__objc_forward_handler
方法
-
ldr
:LDR R0,[R1, #8] ;将存储器地址为R1+8的字数据读入寄存器R0
。这里实际上将x17又去除偏移量, 给到p17中
可看到其实关键是调用了一个__objc_forward_handler
, 我们全局搜索一下可发现是一个C++方法, 在objc-runtime.mm
中, 看下_objc_forward_handler
可看到报错信息... unrecognized selector sent to instance XXX...
就是我们经典的报错方法, 并且其中可看到底层是不区分类方法 +
与对象方法/实例方法 -
的。"+"/ "-"在这里是人为拼接上的。我们之前在Clang
也看过类方法在是元类调用实例方法形式存在的
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
调用地方需要返回再看lookUpImpOrForward
方法, 如果父类链
都没找到就令imp = forward_imp;
最终方法如果没有找到就会往下走return imp
即return forward_imp
, 报那个经典方法找不到错误。
动态方法决议
-
resolve
: 决定, 决议 -
instance
: 实例 -
method
: 方法
实际上, 当父类链
查找完, 也没有找到imp
时并不是直接 return imp
(imp = forward_imp
)中间还会进行一次动态方法决议resolveInstanceMethod
操作。
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
我们先看下resolveMethod_locked
方法
/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
...
if (! cls->isMetaClass()) {
// 当前类不是元类, 即查找的是实例方法
// try [cls resolveInstanceMethod:sel]
// 实例方法的动态方法决议流程
resolveInstanceMethod(inst, sel, cls);
}
else {
// 当前类是元类, 即查找的是类方法
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 类方法的动态方法决议流程
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 如果有动态方法决议, 处理了, 就调用lookUpImpOrForwardTryCache重新帮你处理下
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
可以理解成, 当前方法都imp
没找到, 苹果系统给一次补救机会即动态方法决议
实例方法
→resolveInstanceMethod
类方法
→resolveClassMethod
resolveInstanceMethod
先看下源码
/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
...
// 定义一个resolveInstanceMethod 的方法编号resolve_sel
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 判断当前类, 父类等(NSObject默认有决议方法) 是否有决议方法resolveInstanceMethod, 根本没写就直接返回, 这也是为什么我上面例子为何报 unrecognized selector sent to instance XXX
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, 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 = lookUpImpOrNilTryCache(inst, sel, cls);
....
}
通俗讲, 动态方法决议其实调用了一个决议方法, 拿实例方法举例就是resolveInstanceMethod
, 如果当前imp
没有, 会去父类链查找(NSObject有)。resolveInstanceMethod
, 而这个方法里面处理了我们查找的sel
, 实现、转发等等, 那么系统也算你成功并不会报错。
我们拆分来看
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/authenticated/true)))
定义一个决议方法SELresolve_sel
, 然后走下面if判断, 是否存在
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 判断是否存在动态决议resolveInstanceMethod方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
// 决议者或者决议方法不存在就直接retrun出去
return;
}
其中第三个参数 cls->ISA(/*authenticated*/true)
即cls->ISA(true)
inline Class
objc_object::ISA(bool authenticated)
{
ASSERT(!isTaggedPointer());
return isa.getDecodedClass(authenticated);
}
往下调用isa.getDecodedClass(true)
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA //真机为1, 否则为0
// 判断是否为nonpointer, 通常已经初始化完毕的类, isa中nonpointer为nil
if (nonpointer) {
return classForIndex(indexcls);
}
// 返回当前类
return (Class)cls;
#else
// 非真机getClass(true)
return getClass(authenticated);
#endif
}
返回lookUpImpOrNilTryCache
可发现是调用_lookUpImpTryCache方法
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}