iOS Runtime中如何通过SEL找到IMP
objc_msgSend(id self, SEL _cmd, ...) 中SEL找到IMP的流程
以下是苹果开源代码中Runtime底层的部分删减后的汇编代码
ENTRY _objc_msgSend
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative)
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
b.eq LReturnZero // nil check
END_ENTRY _objc_msgSend
1.首先会进入汇编中 ENTRY _objc_msgSend
2.如果指针小于等于 LNilOrTagged
直接return
返回
3.通过isa
找到相应类class
4.CacheLookup : NORMAL
CacheLookup
源码中注释为:
Locate the implementation for a selector in a class method cache.
在类的方法缓存中 为 SEL
定位到 IMP
。
5.此时全局搜索CacheLookup
,在objc_msg_arm64.s中会找到其对应的宏CacheHit
、CheckMiss
、add
。
.macro CacheLookup
1: CacheHit $0 // call or return imp
返回IMP,并执行
<1> MESSENGER_END_FAST 结束快速查找路径
<2> GETIMP 返回IMP
<3> LOOKUP
2: CheckMiss $0 // miss if bucket->sel == 0
执行
<1> GETIMP LGetImpMiss 没找到IMP
<2> __objc_msgSend_uncached
<3> __objc_msgLookup_uncached
3: add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4)
执行
<1> CacheHit
<2> CheckMiss
<3> JumpMiss
6.当执行__objc_msgSend_uncached
时,说明没有相应缓存。将会在__objc_msgSend_uncached
中执行MethodTableLookup
。
STATIC_ENTRY __objc_msgSend_uncached
MethodTableLookup
END_ENTRY __objc_msgSend_uncached
在MethodTableLookup
的宏定义中会发现__class_lookupMethodAndLoadCache3
全局搜索__class_lookupMethodAndLoadCache3
发现没有什么多余的东西可以看,尝试去掉前边的_
后发现新大陆!
汇编通过_class_lookupMethodAndLoadCache3
方法跳转到了C,并且调用了lookUpImpOrForward
7.lookUpImpOrForward
的调用传值
lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/ cache, YES/*resolver*/);
其中,cache
传了NO
,是由于先前汇编部分已经没有在缓存中找到对应的IMP,所以没有必要传YES
。
8.lookUpImpOrForward
的调用过程
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
//再查一查缓存,万一有了呢
//因为OC动态语言,随时随地都能操作修改,防止数据问题,再取一次
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.read();
//如果Class没有声明,就声明它
if (!cls->isRealized()) {
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
//如果Class没有初始化,就初始化它
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
//开始反复尝试方法查找
retry:
runtimeLock.assertReading();
// Try this class's cache.
//再查一查缓存,万一有了呢
//因为OC动态语言,随时随地都能操作修改,防止数据问题,再取一次
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 在当前类的方法缓存列表中查找
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 在当前类的父类的方法缓存列表中查找
{
unsigned attempts = unreasonableClassCount();
//for循环,不断往父类查找,父类的父类,直到父类没有父类,
//也就是直到curClass == NSObject
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.");
}
//查找父类的方法缓存
imp = cache_getImp(curClass, sel);
if (imp) {
//如果找到这个IMP,并且这个IMP不是消息转发的IMP
if (imp != (IMP)_objc_msgForward_impcache) {
// 将这个IMP填充进缓存列表,避免下次重复这波操作
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;
}
}
//在父类的方法缓存列表中找IMP
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//如果找到这个Meth,将这个IMP填充进缓存列表,避免下次重复这波操作
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
//没发现IMP,尝试动态方法解析 !!一次!!
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// 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;
}
//没发现这个IMP,且动态方法解析不起作用,就用消息转发。
// No implementation found, and method resolver didn't help.
// Use forwarding.
//调用消息转发
imp = (IMP)_objc_msgForward_impcache;
//缓存这个IMP
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
9.getMethodNoSuper_nolock
方法调用过程,通过遍历class
的方法列表查找IMP
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
10.动态方法解析 _class_resolveMethod
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
//判断class 是否是元类
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
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
//判断是否有执行动态方法解析,如果不是就Return。
//防止混乱。
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.
//系统自动给要解析的Class发送一条消息,这也是为什么
//这个方法里面又调用了一次lookUpImpOrForward
//+(BOOL)resolveInstanceMethod:(SEL)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 {
// 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));
}
}
}
11.当lookUpImpOrForward
没发现IMP,且尝试_class_resolveMethod
动态方法解析不起作用时,调用消息转发。
消息转发流程.png
消息转发的流程
消息转发流程开源代码中只调用了汇编的方法,没有具体的实现。
我们可以通过instrumentObjcMessageSends
方法查看所有关于这个信息的调用堆栈信息。
extern void instrumentObjcMessageSends(BOOL);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
[LGPerson walk];
instrumentObjcMessageSends(NO);
}
return 0;
}
程序编译结束后,可在路径:硬盘 ->private -> tmp -> msgSends-xxxxx 文件 查看方法调用过程
查看路径.png
msg_Sends文件内容
1.forwardingTargetForSelector
,查询是否有其它对象能够处理该消息。在这个方法里,我们需要返回一个能够处理该消息的对象,如果还是无法处理消息,那么就会做最后的尝试。
2.先调用methodSignatureForSelector:
获取方法签名。
3.然后再调用forwardInvocation:
进行处理,这一步的处理可以直接转发给其它对象,即和forwardingTargetForSelector
的效果等效,但是很少有人这么干,因为消息处理越靠后,就表示处理消息的成本越大,性能的开销就越大。所以,在这种方式下,会改变消息内容,比如增加参数,改变选择子等等。
上一篇: PAT常用函数