如何研究消息查找?
1.通过汇编代码进行跟踪。(需要在可运行源码中进行查看)
- 创建对象,并进打断点在发送消息代码中。
LGPerson *person = [LGPerson alloc];
[person sayNB];
- 查看汇编代码。
0x100000e70 <+64>: callq *0x18a(%rip) ;
(void *)0x00007fff66883040: objc_msgSend
断点打在objc_msgSend中,再进行断点跳转->
0x7fff66883103 <+195>: jmp 0x7fff66883b90 ;
_objc_msgSend_uncached
因为是新创建的方法没有缓存,需要jmp跳转_objc_msgSend_uncached
0x1003430af <+175>: jmp 0x100343a50 ;
_objc_msgSend_uncached
继续跟进_objc_msgSend_uncached查看 ->
0x100343a8f <+63>: callq 0x100343f10 ;
::_class_lookupMethodAndLoadCache3(id, SEL, Class) at objc-runtime-new.mm:4845
::代表是 C++代码,objc-runtime-new.mm表示在新的runtime代码中。_class_lookupMethodAndLoadCache3是进行方法查找和缓存加载。
继续查看 ->
0x100343f3d <+45>: callq 0x100364150 ;
::lookUpImpOrForward(Class, SEL, id, bool, bool, bool) at objc-runtime-new.mm:4865
看到lookUpImpOrForward,就可以进行源码查看了。
2.源码查看
小知识点 - 忽略代码警告的宏定义
#pragma clang diagnostic push
// 让编译器忽略错误
#pragma clang diagnostic ignored "-Wundeclared-selector"
中间添加代码
#pragma clang diagnostic pop
- 前面准备条件
继承关系
LGStudent -> LGPerson -> NSObject
LGStudent:
- (void)sayHello;
+ (void)sayObjc;
LGPerson:
- (void)sayNB;
+ (void)sayHappay;
NSObject+LG:
- (void)sayMaster;
+ (void)sayEasy;
-
1.方法调用:
-
对象 方法调用情况:
- 创建
LGStudent对象,调用自己的方法。
LGStudent *student = [[LGStudent alloc] init]; [student sayHello];- 自己没有,调用父类的。
[student sayNB];- 自己没有,父类没有,调用NSObject的方法。
[student sayMaster];- 自己、父类、NSObject都没有的方法
[student performSelector:@selector(saySomething)];会爆出经典的bug:
unrecognized selector sent to instance 0x103000450 - 创建
-
类 方法调用情况:
- 调用自己的方法。
[LGStudent sayObjc];- 自己没有,调用父类的。
[LGStudent sayHappay];- 自己没有,父类没有,调用NSObject的方法。
[LGStudent sayEasy];- 自己、父类、NSObject都没有的方法
[student performSelector:@selector(sayLove)];也会报出经典的bug:
unrecognized selector sent to instance 0x103000450- 调用
NSObject+LG中的sayMaster方法,即 类 调用 对象方法。
[LGStudent performSelector:@selector(sayMaster)];结果是可以调用的,控制台打印
-[NSObject(LG) sayMaster]原因:是按照isa的走位图进行方法寻找。
类中查找实例方法 -> 元类中查找类方法 -> 根源类中查找元类的方法 -> NSObject中查找根源类的方法。
在NSObject中的是实例方法,找到对应的方法会返回。
NSObject中未找到的话,就会报错。
-
-
2.方法慢速查找流程分析
在快速查找方法中,没有查找到,才会找到
_class_lookupMethodAndLoadCache3方法,进行慢速查找。在对汇编代码跟中得知,最后方法查找到的是
_class_lookupMethodAndLoadCache3方法,下面进行源码查看。-
方法查找流程:
- 搜索到
_class_lookupMethodAndLoadCache3方法,extern是为了外部可以调用到此函数
extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class); IMP _class_lookupMethodAndLoadCache3(id obj(对象), SEL sel(方法名), Class cls(类)) { return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/); }-
lookUpImpOrForward查看(查看重点代码)并进行代码段分析。cache查询
进行
cache查询,说明此接口可以在多处调用,有cache时,进行imp的获取并返回。if (cache) { imp = cache_getImp(cls, sel); if (imp) return imp; }runtimeLock.lock();进行上锁操作,防止多个方法进行查找、返回时出现返回错误。checkIsKnownClass(cls);类进行判断,未被编译器加载,则返回一个未知的结果。- 判断类是否实现,实现直接返回该类,查看
realizeClass方法,发现是查找rw、ro中属性、方法等的实现情况,是为了下面的函数执行做准备。
if (!cls->isRealized()) { realizeClass(cls); }- 判断时候进行了初始化。
if (initialize && !cls->isInitialized()) { ... }
上面这些其实是在为慢速查找做的准备。
-
查找,在
retry中进行分情况进行查找。-
类 的缓存中查找
// Try this class's cache. imp = cache_getImp(cls, sel); if (imp) goto done; -
类 的方法列表中查找,此查找中二分法查找
如果查找到缓存,则调用
log_and_fill_cache函数进行缓存// 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 do not 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; } } } -
没有实现。尝试方法解析器一次。
查看
_class_resolveMethod的函数实现,发现先对元类判断,是不是元类则进行实力方法查找,是元类就进行类方法查找,着也验证了上面 类 调用NSObject的实例方法可以实现的原因。在消息转发中会对
_class_resolveMethod进行解释。// No implementation found. Try method resolver once. if (resolver && !triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); // Do not cache the result; we do not hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; } -
没有找到实现,方法解析器也没有帮助。则使用转发机制。
_objc_msgForward_impcache消息转发函数// No implementation found, and method resolver did not help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); -
去掉线程锁
done: runtimeLock.unlock();
-
上面方法都为找到的情况下,会走这个方法
_objc_msgForward_impcache-
查找
_objc_msgForward_impcache方法。- 继续查找发现`_objc_msgForward_impcache`未实现。 - 全局搜索也找到的是调用,没有找到实现。 - 在汇编代码中发现了`STATIC_ENTRY __objc_msgForward_impcache` ``` STATIC_ENTRY __objc_msgForward_impcache // No stret specialization. 调用 __objc_msgForward b __objc_msgForward END_ENTRY __objc_msgForward_impcache 进入__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`方法,搜索没有找到实现方法,前面知道汇编和c语言方法区别是 差一个`_`下划线,搜索`_objc_forward_handler`。 -
在c语言中查找
_objc_forward_handler方法。- 返现会被赋值
objc_defaultForwardHandler函数void *_objc_forward_handler = (void*)objc_defaultForwardHandler; - 查看函数。
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); }"%c[%s %s]: unrecognized selector sent to instance %p " "(no message forward handler is installed)
- 返现会被赋值
惊讶!原来系统的报错方法是在这里!!!
- 搜索到
-
-
消息转发
其实在程序奔溃之前,系统会进行消息转发进行补救。
方法查找流程分为 快速查找 、 慢速查找
消息转发机制 也分为 快速转发 、 慢速转发
在“方法的慢速查找”中,知道会有走一个方法
if (resolver && !triedResolver) {}方法。-
进入
_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); } } }在
if (! cls->isMetaClass()) {}是判断是否是元类,不是元类走的是对象方法,是元类走的类方法。-
类方法走
_class_resolveClassMethod中的方法,会进行一个查找lookUpImpOrForward因为实例方法在类中,类方法在元类中。所以会先走
else中的_class_resolveClassMethod函数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); ... }会进行一次
lookUpImpOrNil查找,再走一次lookUpImpOrForward。 -
_class_resolveClassMethod中进行一个查找之后未发现,就会走else中的lookUpImpOrNil方法。if (!lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); }再次查找,为找到会进入到
_class_resolveInstanceMethod方法。 -
_class_resolveInstanceMethod中走lookUpImpOrNil进行第二次查找。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; } ... } -
第二次查找未找到,会一直找到根源类,会走
if (! cls->isMetaClass()) {}中的_class_resolveInstanceMethod函数进行第三次实例方法查找。if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] _class_resolveInstanceMethod(cls, sel, inst); }
- 在
_class_resolveClassMethod的三次查找中都未找到imp,则triedResolver会被标记成YES,再进行goto retry操作,重走一遍,但是不会进入到if (resolver && !triedResolver) {}方法中,并且会发送
if (resolver && !triedResolver) { methodListLock.unlock(); _class_resolveMethod(cls, sel, inst); triedResolver = YES; goto retry; }- 在
_class_resolveClassMethod中只要能找到imp,则会走goto done;done: methodListLock.unlock(); return methodPC;
以为着会后走报错的
_cache_addForwardEntry函数。
-
-
从上面的代码分析中可以得知
_class_resolveInstanceMethod函数,会被多次调用,那么在外面合适的地方进行重写此方法,并进行处理,就不会出现崩溃显现。- 重写
_class_resolveInstanceMethod进行 消息转发。
+ (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(saySomething)) { NSLog(@"说话了"); 获取需要执行此转发任务方法的 imp IMP sayHIMP = class_getMethodImplementation(self, @selector(sayHello)); 获取需要执行此转发任务的 方法 Method sayHMethod = class_getInstanceMethod(self, @selector(sayHello)); 需要的type类型 const char *sayHType = method_getTypeEncoding(sayHMethod); return class_addMethod(self, sel, sayHIMP, sayHType); } NSLog(@"来了 老弟 - %p",sel); return [super resolveInstanceMethod:sel]; }执行代码
[student saySomething];执行结果
2019-12-31 10:01:12.516446+0800 LGTest[1434:34340] 说话了 2019-12-31 10:01:12.517101+0800 LGTest[1434:34340] -[LGStudent sayHello] Program ended with exit code: 0 - 重写
消息转发流程图:
-
