和谐学习!不急不躁!!我是你们的老朋友小青龙~
前面文章iOS底层分析之类的探究-cache之 insert、objc_msgSend已经分析了objc_msgSend从快速消息查找到慢速查找的流程,在慢速查找的函数是lookUpImpOrForward
,里面的查找流程是:
- 当前类方法列表查找
- 父类缓存查找
- 父类方法列表查找
- 继续从父类的父类开始循环第二步的操作,直到父类为nil,给imp返回一个默认的
forward_imp
lookUpImpOrForward部分代码如下
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
...
for (unsigned attempts = unreasonableClassCount();;) {
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
...
///当遍历所有父类都找不到,给imp赋值forward_imp
imp = forward_imp;
break;
}
}
...
}
那么我们来探索下_objc_msgForward_impcache
的流程实现:
(搜索思路
:当前文件优先搜索,找不到了再全局搜索,必要时可以去掉前面的“_
”或"_ _
"然后再搜索)
STATIC_ENTRY __objc_msgForward_impcache
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就是跳转进入$0参数也就是x17,
x17就是__objc_forward_handler,
所以__objc_msgForward就是进入__objc_forward_handler
*/
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
全局搜索“_objc_forward_handler
”
__attribute__((noreturn, cold)) 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);
}
//_objc_forward_handler就是objc_defaultForwardHandler
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
看到这里,是不是很熟悉,我们平时写OC代码的时候,如果调用了一个只声明未实现的方法,控制台就会打印这段话,说归说,还是要动手写一下的:
-
实例方法
-
类方法
我们甚至还在源码里看到,控制台打印的报错方法的“+
”“-
”都是手动添加上去,所以这也说明了,在底层源码里,没有类方法这一说,都是对象方法。
OK,到这里我们已经知道了缓存imp找不到就去方法列表找,方法列表也找不到,就返回一个默认的imp并且抛出异常,那么有没有什么办法可以应对这种找不到imp的情况?接下来,我们回到lookUpImpOrForward
函数,我们看到这样一段代码:
消息动态决议 探索
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
...
// No implementation found. Try method resolver once.
/** 从注释可以看出,当imp找不到的时候,会有一次解决的应对措施,
而且这个if语句还是个单例,感兴趣的朋友可以看看下面的“单例解释”
*/
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
//计算结果:behavior = 1;
return resolveMethod_locked(inst, sel, cls, behavior);
}
...
}
搜索“resolveMethod_locked
”
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
...
///如果不是元类就调用resolveInstanceMethod
if (! cls->isMetaClass()) {
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
///如果是元类就调用resolveInstanceMethod
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
///此刻的behavior = 1
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
/**
*/
下面看看resolveInstanceMethod
都干了啥,
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
...
SEL resolve_sel = @selector(resolveInstanceMethod:);
...
/** 给 cls类发送消息,判断是否实现 resolveInstanceMethod:方法,参数是当前的sel
常规操作是在resolveInstanceMethod:方法里,对当前类添加sel;
比如 class_addMethod([self class],sel, (IMP)sayHello,"v@:@");
*/
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
///然后再查找一遍,看看有没有sel
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
/** PrintResolving是配置的其中一种环境变量,
目的是打印 resolveClassMethod: 和 +resolveInstanceMethod: 的方法日志;
目前PrintResolving返回都是false,所以下面的if可以暂时不用取看它*/
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));
}
}
/**
resolveInstanceMethod函数干了两件事情:
1、判断是否实现resolveInstanceMethod
2、继续调用lookUpImpOrNilTryCache查找缓存里sel对应的imp
*/
}
下面看看lookUpImpOrNilTryCache
都干了啥,
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
//查询缓存里的imp,如果behavior不传,那behavior就等于0
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//如果cls类从来没调用过Initialized函数,就调用lookUpImpOrForward方法
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 如果cls已经初始化过,就调用cache_getImp获取缓存里的imp
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;// 如果imp不为空,就调用done
//如果imp为空,继续下面if判断
#if CONFIG_USE_PREOPT_CACHES //CONFIG_USE_PREOPT_CACHES:是否有缓存
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
// 如果imp为空,就调用lookUpImpOrForward
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
// 如果(behavior & LOOKUP_NIL)为真,且imp等于_objc_msgForward_impcache就返回nil
// _objc_msgForward_impcache就是lookUpImpOrForward函数里创建的forward_imp
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
这里解释 为什么resolveMethod_locked调用了两次_lookUpImpTryCache
:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
...
resolveInstanceMethod(inst, sel, cls);
...
//lookUpImpOrForwardTryCache里面会调用_lookUpImpTryCache
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
///lookUpImpOrNilTryCache里面会调用_lookUpImpTryCache
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
...
done:
/**
* 我们发现:
* 由resolveInstanceMethod进入到这里,behavior=4;
* 由lookUpImpOrForwardTryCache,behavior=1;
* behavior=4即表示前面系统已经给了一次补救机会,如果此时查到的imp
仍旧是默认的_objc_msgForward_impcache,
那说明根本就没有实现resolveInstanceMethod: 或 resolveClassMethod: 方法,
所以会返回nil;
*/
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
我们发现:
- 由
resolveInstanceMethod
进入到_lookUpImpTryCache,behavior
传入的是4
,这是因为要做非空判判断,因为调用之前系统给了一次补救imp找不到的机会; - 由
lookUpImpOrForwardTryCache
进入到_lookUpImpTryCache,behavior
传入的是1
,这个1是由前面lookUpImpOrForward
函数带过来的
单例解释
//接下来解释,为什么这个if内部语句只会调用一次
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
解释如下:
(补0的位置不参与比较)
|运算是把左右两边的相加
举例:2|1 即0010 + 0001 = 0011,也就是3
& 运算是把左右两边的值,每一位进行比较,相同输出1,不同输出0
举例:3&2 即0011 & 0010 ,第三位相同,输出1,第四位不同,输出0,结果是0010也就是2;
^ 运算跟&刚好相反,不同输出1,相同输出0
举例:3&2 即0011 & 0010 ,第三位相同,输出0,第四位不同,输出1,结果是0001也就是1;
简单记忆:
| 相加
& 找相同
^ 找不同
回到上面的问题,
_lookUpImpOrForward全局搜索可知等于3,LOOKUP_RESOLVER是一个常量2,
3&2 = 2,所以if判断结果为true;
behavior = behavior^LOOKUP_RESOLVER = 3^2 = 0011^0010 = 0001 = 1
下次进来的时候,if判断就变成1&2 = 0001&0010 = 0,不会进入判断
枯燥的源码分析终于告一段落,接下来开始实战测试:
这个是没有实现resolveInstanceMethod:
:
接下来在DirectionChild里加上代码:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod class:%@ - selName:%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
我们发现resolveInstanceMethod:
执行了两次,这里大胆儿猜测第二次是元类,但是控制台打印发现两个self地址一样
,这个猜测被否定了:
我们继续回到源码分析原因,在resolveInstanceMethod函数里,在图片所在“msg(cls, resolve_sel, sel)
”这一行打上端点,然后运行,进入断点的时候打印下cls的地址:
通过分析,我们发现resolveInstanceMethod函数里,执行了好几遍objc_msgSend消息发送,其实两次是对DirectionChild及其元类发送消息,所以也就解释了上面“+ (BOOL)resolveInstanceMethod:(SEL)sel
”为什么会调用两次。
后续发现,这里的解释有点不那么通顺,这里再补充解释下:
在resolveInstanceMethod:
方法里打上断点
,因为我们好奇的是为什么第二次会进来,所以第二次
进入断点的时候,控制台输入bt
打印一下堆栈信息:
我们发现,在返回main之前,还调用了一下CoreFoundation框架里的___forwarding___
函数,(关于___forwarding___的探索,可以看消息转发文章拓展部分),而我们知道,___forwarding___内部是调用了class_respondsToSelector
函数,我们打开objc源码搜索class_respondsToSelector
可以看到:
继续点击
_lookUpImpTryCache
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
...
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
...
}
会发现其内部,当imp为空的时候,调用了lookUpImpOrForward,所以我们可以理解为,假如imp不为空,就不会进入这里,也就是不会二次调用resolveInstanceMethod:
,要实现这个目的那就在resolveInstanceMethod:
返回一个可以新的消息接收者
。
完善上述resolveInstanceMethod
代码:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod class:%@ - selName:%@",self,NSStringFromSelector(sel));
if(sel == @selector(sayHello)) {
Method method =class_getInstanceMethod(self,@selector(saySomething));
///添加对象方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)saySomething{
NSLog(@"What are you doing ?");
}
控制台能够正常打印,且resolveInstanceMethod
只执行一次;
类方法
也同样如此,可以实现以下动态决议:
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"resolveClassMethod class:%@ - selName:%@",self,NSStringFromSelector(sel));
if(@selector(sayHowAreYou) == sel){
Class metaClass = objc_getMetaClass("DirectionChild");
IMP imp = class_getMethodImplementation(metaClass, @selector(saySSJ));
Method md = class_getInstanceMethod(metaClass, @selector(saySSJ));
const char * type = method_getTypeEncoding(md);
return class_addMethod(metaClass,sel, imp,type);
}
return [super resolveClassMethod:sel];
}
+ (void)saySSJ{
NSLog(@"=========saySSJ !!");
}
那么问题来了,如果resolveInstanceMethod:
或 resolveClassMethod:
都没有实现,又该怎么办呢?
欲知后事,请看下篇文章《iOS底层分析之objc_msgSend消息转发》
动态决议代码
百度网盘:pan.baidu.com/s/1_1B0sSeq…
密码:zsxn
更新日志:
2021.07.15 17:45 -> 增加 resolveInstanceMethod:
执行了两次的解释