iOS 底层探索篇 ——Runtime-objc_msgSend流程分析 - 慢速查找流程 (下)

方法无法找到流程分析

上文说到,如果所有的父类都找完了还没找到方法,那么imp就会设为forward_imp,那么继续往下走,看到会返回imp也就是forward_imp
在这里插入图片描述
看到forward_imp在方法开头有进行赋值。
在这里插入图片描述
接着寻找_objc_msgForward_impcache,发现进入__objc_msgForward。
在这里插入图片描述
接着搜索__objc_msgForward,看到TailCallFunctionPointer x17.
在这里插入图片描述
搜索TailCallFunctionPointer,发现是跳转$0,也就是x17 __objc_forward_handler
在这里插入图片描述
接下来搜索 __objc_forward_handler。
在这里插入图片描述
发现没有找到相应的实现,那么就说明这里从汇编跑到了c语言,搜索objc_forward_handler,!objc2不看看到_objc_forward_stret_handler被赋值为objc_defaultForwardStretHandler,接下来看objc_defaultForwardStretHandler函数,发现这里会进行格式化的打印,也就是输出当方法找不到的时候的打印。这里判断是不是元类,来进行判断是不是类方法,从而判断输出+号还是-号,然后在打印通过object_getClassName里面调用obj->getIsa()返回的class,然后打印SEL,最后打印self的位置。
在这里插入图片描述
比如
在这里插入图片描述
那么发生了错误,有没有办法进行处理呢?接下来,就进入了消息处理流程。

对象方法动态方法决议

运行一个不存在的方法,并打下断点。
在这里插入图片描述
然后在lookUpImpOrForward中打下断点后进来。

在这里插入图片描述

运行起来后,输出一下cls确认消息接受者是对的,这里是LGPerson是对的。
在这里插入图片描述
在输出一下imp,发现是空的,因为没有这个方法所以是对的。
在这里插入图片描述
往下走后进入了这里,这里的behavior是个方法参数。
在这里插入图片描述
搜索一下behavior是哪里来的,并且值是多少。发现在MethodTableLookUp里面,并且值为3.
在这里插入图片描述
回到loopUpImpOrForward,点进去看LOOKUP_RESOLVER是什么
在这里插入图片描述
所以behavior & LOOKUP_RESOLVER 也就是 3 & 2 = 0010 = 2,接下来 behavior ^= LOOKUP_RESOLVER; 也就是 behavior(3) ^= 2 就是1了。当behavior为1,那么 if (slowpath(behavior & LOOKUP_RESOLVER)) 就永远为false,就永远进不来了,也就是说,这里的方法只执行一次。
接下来进入resolveMethod_locked方法。
在这里插入图片描述
这里不是元类,所以走到resolveInstanceMethod。
在这里插入图片描述
发现这里又有一个trycache,这是为什么呢?其实这里的trycache,是把方法放到缓存里面,可以防止下一次的trycache查找缓存。具体看下文这里接着流程。这里其实给了一次机会,如果实现了resolveInstanceMethod:,就会有一个容错处理。
看到这里是向方法发送消息,所以是类方法。
在这里插入图片描述
去实现一下

在这里插入图片描述
运行一下得知,在报错之前,来到了resolveInstanceMethod方法,self是 LGPerson, sel是sayasuofg。并且注意到resolveInstanceMethod调用了两次。
在这里插入图片描述
那么报错之前能来到resolveInstanceMethod方法,意味着我们能在resolveInstanceMethod里面处理而避免报错。

尝试一下在resolveInstanceMethod里面addMethod 来避免报错。

在这里插入图片描述
在这里插入图片描述

运行一下。
在这里插入图片描述
发现不报错,并且成功调用了testfunc方法。
在resolveInstanceMethod中的lookUpImpOrNilTryCache,如果没有实现resolveInstanceMethod方法,会不会return呢?
在这里插入图片描述
答案是不会的,因为在nsobjct中有默认实现,返回NO。因为如果在resolveInstanceMethod过程中返回return报错的话,系统会更加的不稳定,所以系统实现了这个方法为我们兜底。
在这里插入图片描述

类方法动态方法决议

添加一个没有实现的类方法,然后运行。

在这里插入图片描述

运行后,方法会走到else 里面,
在这里插入图片描述
接着会调用resolveClassMethod方法,和对象方法动态决议很相似。
因为类方法存在元类里面,为了防止元类没有初始化,这里对元类进行了操作。
接着看到这里的resolved 是调用resolveClassMethod进行处理的,所以应该在resolveClassMethod方法里面进行处理。那么resolveClassMethod应该写在哪里?这里的消息接受者是元类,元类的对象方法就是类的类方法,所以应该写在类里面。

在这里插入图片描述
写一下resolveClassMethod方法。

在这里插入图片描述

运行一下发现进来了。
在这里插入图片描述
接着进行方法替换。类方法在元类里面所以要获取metaclass。
在这里插入图片描述
在这里插入图片描述

运行一下发现替换成功。

往回走,发现resolveClassMethod下面调用了一次resolveInstanceMethod,这是为什么呢?这里因为类方法不仅以类方法的形式存在,还以元类对象方法的形式存在,所以也会有对象的继承链的形式存在,最终会走到NSObject里面。
在这里插入图片描述

在这里插入图片描述

既然对象方法和类方法最终都走到NSObject,那么可不可以都在NSObject处理呢?来试一下。

在这里插入图片描述
运行一下,发现方法都被替换了,说明是可行的。
在这里插入图片描述
那么为什么要有动态决议这样子的处理呢。

  1. 方法查找是通过sel找imp的过程,当通过sel找不到imp的时候,苹果给一次机会去纠正以免报错。
  2. 全局的方法找不到imp的时候,我们都可以监听。这样我们可以监听我们自己写的方法,可以进行记录下来以便更改并进行处理避免奔溃,比如跳转到首页的处理。

这种方式就叫做AOP。AOP也就是面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

为什么两次tryCache

跟着流程走一遍
lookUpImpOrNilTryCache
在这里插入图片描述
_lookUpImpTryCache
在这里插入图片描述
lookUpImpOrForward
在这里插入图片描述

log_and_fill_cache,到这里,cls的缓存里面插入了sayasuofg的imp
在这里插入图片描述

所以到lookUpImpOrForwardTryCache进来时
在这里插入图片描述
imp就不是nil,而是(libobjc.A.dylib_objc_msgForward_impcache),就不会在进行慢速查找,而是直接去done,然后返回 (libobjc.A.dylib_objc_msgForward_impcache)

在这里插入图片描述

在这里插入图片描述

instrumentObjcMessageSends辅助

可以在代码中添加instrumentObjcMessageSends查看调用了哪些方法。
添加未实现代码并运行。
在这里插入图片描述
到Macintosh HD 中的隐藏文件tmp中。
在这里插入图片描述

找到msgSends开头的文件打开。查看
在这里插入图片描述

在这里插入图片描述

汇编指令参考.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值