iOS底层探索--动态方法决议&消息转发流程

前言

   在iOS底层探索--方法慢速查找篇章中如果慢速查找找不到Methodimpimp = _objc_msgForward_impcache然后break跳出死循环,在return_objc_msgForward_impcache\color{#ff0000}{之前}会进行一次方法决议:resolveMethod_locked(inst, sel, cls, behavior),给一次机会

一、_objc_msgForward_impcache汇编流程

//******** __objc_msgForward_impcache**********************************************************
STATIC_ENTRY __objc_msgForward_impcache
    // No stret specialization.
    b   __objc_msgForward   // 调转__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

//******** TailCallFunctionPointer**********************************************************
.macro TailCallFunctionPointer
    // $0 = function pointer value
    br  $0  // 直接根据寄存器寻址调转寄存器0的地址
.endmacro

__objc_msgForward_impcache的汇编里面就一句代码,就是调转__objc_msgForward,而这个流程中__objc_msgForwardTailCallFunctionPointer里面就是寄存器寻址调转,也就是调转$0寄存器,这个寄存器就是下面两句之后的值:

adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
也就是拿到_objc_forward_handler,然后调用,在objc源码全局搜索一下发现在objc-runtime.mm文件中找到他的赋值,类似一个句柄,在底层实现,在非OBJC2中是默认是空的,在OBJC2中默认等于 objc_defaultForwardHandler就是打印一些东西,这就可以解释了这个经典的错误:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PSYPerson happy]: unrecognized selector sent to instance 0x101021350'
底层也提供了自定义定制的API:void objc_setForwardHandler

#if !__OBJC2__    // 非OBJC2**************************

// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;

#else

// // OBJC2的默认值*************************
__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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn, cold)) struct stret
objc_defaultForwardStretHandler(id self, SEL sel)
{
    objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif

#endif
// 支持定制
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
#if SUPPORT_STRET
    _objc_forward_stret_handler = fwd_stret;
#endif
}

二、 动态方法决议

方法决议流程如下:
  1. 如果 非元类 !cls->isMetaClass() ,也就是如果是对象方法
    • 调用对象的解析方法 resolveInstanceMethod(inst, sel, cls);
      • 查找这个方法resolveInstanceMethod:,如果没有找到就直接停止解析。(其实这个方法是一定有的,在NSObject中已经实现,返回了NO,相当于系统兜底了)
      • 调用objc_msgSend(cls, resolveInstanceMethod:, sel), sel是一个参数
      • 缓存结果将+resolveInstanceMethod:方法添加到类的cache中,下次resolver解析器就不执行了imp = lookUpImpOrNilTryCache(inst, sel, cls);
  2. 如果是元类 ,即调用的类方法
    • 调用类的解析方法 resolveClassMethod(inst, sel, cls);
      • 查找这个方法resolveClassMethod:,如果没有找到就直接停止解析。(其实这个方法是一定有的,在NSObject中已经实现,返回了NO,相当于系统兜底了)
      • 调用objc_msgSend(cls, resolveClassMethod:, sel), sel是一个参数
      • 缓存结果将+resolveClassMethod:方法添加到元类cache中,下次resolver就不执行了imp = lookUpImpOrNilTryCache(inst, sel, cls);
    • 如果尝试查找元类的缓存
    • 如果元类中缓存为空,则解析原类中的对象方法resolveInstanceMethod(inst, sel, cls);,因为类方法在元类中是对象方法
  3. 因为上面流程可能已经指向一个对象的某个SEL了,所以继续lookUpImpOrForwardTryCache(inst, sel, cls, behavior);走方法查找流程

既然底层给了一次机会解析,那我得兜着,实现一下动态解析这两个方法:运行之后发现无论是调用没实现的对象方法,或者是类方法都打印了两次:对象方法动态决议:happy类方法动态决议:happy,为什么呢?不是给了一次机会而已吗?

实现动态解析这两个方法
对象方法

类方法

走两次,在这这疑问,在源码中,下断点,打印第一次进入lookUpImpOrForwardresolveMethod_locked函数调用栈和第二次进入时的函数调用栈,期间会调用很多次这个方法,我们需要认准sel 和 cls是同一个即可:
第一次进lookUpImpOrForward

第一次进resolveMethod_locked

第二次进lookUpImpOrForward

第二次进resolveMethod_locked


  由上可知,在第二次进入的时候,instancenil值,behavior = 2,behavior & LOOKUP_RESOLVER = 1, behavior ^= LOOKUP_RESOLVERbehavior = 0;并且从函数调用栈可知,其是CodeFundation框架的_CF_forwarding_prep_0触发的,但是我们去苹果官网文档或者开源库中查找并没有看到开源的源码。带着这疑问,我们在消息转发流程里面探索。我们回到resolveInstanceMethod:方法上,既然苹果给了一次解析机会,我们可以在这个方法中调用Runtime的API动态的给class添加sel,甚至指定一个IMP,来达到解决崩溃的目的,但是问题又来了,崩溃的信息是这样-[PSYPerson happy]: unrecognized selector sent to instance 0x101021350,从resolveInstanceMethod:到崩溃信息是否还有其他流程,如果有,在resolveInstanceMethod :这个方法中暴力的截获,万一苹果在后面的流程中做了啥操作呢,显然不能这么干。

三、消息的转发流程

  我们在方法的流程中,慢速查找方法的时候,有个函数log_and_fill_cache,填充缓存并有一个log打印,根据objcMsgLogEnabled为YES时在临时文件/tmp/msgSends-xxxx打印,而objcMsgLogEnabled默认是false,它的设置是在方法void instrumentObjcMessageSends(BOOL flag),我们可以在声明外部函数只打印调用未实现的方法的流程,并在/tep/路径下看到msgSends-xxx文件的打印如图:


image.png

可以看到resolveInstanceMethod:之后还有forwardingTargetForSelector:methodSignatureForSelector:最后才到doesNotRecognizeSelector:并且方法动态决议的时候走了两次,第二次进来是由CodeFundation框架的_CF_forwarding_prep_0触发的。这其中的流程可以使用CodeFundation库借助IDA或者hopper看看伪代码流程或者汇编流程。感兴趣的朋友可以慢慢看一下,文章就不带大家看了,所以得出整个消息转发的流程如下图:

消息转发流程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容