前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >objc_msgSend底层探索(上)

objc_msgSend底层探索(上)

作者头像
CC老师
发布2022-01-14 15:10:35
1570
发布2022-01-14 15:10:35
举报

我这篇文章呢,主要来分析一下objc_msgSend,关于他的一个执行流程和快速查找的过程,那首先我需要了解一下Runtime是怎么调起底层的呢?也就是Runtime是怎么发起的呢?Runtime的发起方式一共有三种。

第一种就是直接从OC层面,调用相关的方法,第二种就是通过NSObject,调用相关的接口,第三种,就是底层提供的objc这类的下层的api,什么意思呢。

在整个的OC层面我们来看一下,第一点,我们写代码都是在OC的Code这一层,就是下图的第一层,那么很多的相关的Framework、Service,是在第二层,Runtime Api也在这一层,比如说objc_getClass、objc_Method等等一些相关的属性的一些接口,就都在这里,然后第三层是Compiler,编译器。

最下面的,就是Runtime System Library,就是底层的一些相关的库,这些库的上层的还原,就是通过编译器从中间层进行拦截来提供一个支持,比如说,还可以提供NSObject的相关的一些接口、Runtime的相关的一些接口,这就是runtime发起的三种形式。

我们再来落地一下,哪三种形式呢,如下图,比如说OC Code,person调用一个方法sayHello,就是调起了Runtime,调起了方法的底层的一些东西。

然后,比如我调用isKindOfClass的时候,就是调用的NSObject的一些相关的接口,Runtime API有哪些呢?比如说,class_getInstanceSize,这就是底层的API。

然后来看代码实现,如下图,我写了一个LGPerson,然后他有一个sayHello和一个sayWorld方法,但是sayWorld是没有进行实现的,sayHello有实现,我在command+B的时候。

提示了Build Succeeded,也就是编译通过了,但是我运行起来之后就报错了,这就是运行时和编译时的区别。

然后我来看底层api是怎么来实现的,打开终端,cd到当前的main.m文件的目录下,输入clang命令:clang -rewrite-objc main.m -o main.cpp,稍等片刻就得到了一个cpp文件,在文件夹中打开它,然后来到最后找到main函数,如下图。

main函数里面的东西,就等效于我的OC代码,在这个重写的过程中,就有一些编译时的相关处理,比如说,我的sayWorld方法,他并不是仅仅跟看起来一样只有一个sayWorld的方法,他在底层做了一些什么事情呢?

可以很直观的看到,有一个函数叫做objc_msgSend,他有两个参数,第一个是person,第二个是sel_registerName("sayWorld”),这就是Runtime的三种发起方式里面的第三种方式,也就是API。

然后可以看到,我在进行alloc的时候,底层也是一个objc_msgSend函数,他第一个参数是objc_getClass("LGPerson"),而sayWorld是传的一个普通的person对象,也就是说,我进行任何的方法的调用,他在底层的名字都叫做objc_msgSend,直观的翻译过来就是消息的发送。

这个函数有两个参数,就意味着消息发送需要两个非常重要的东西,第一个就是这里的person,或者是LGPerson,也就是消息的接收者,第二个是消息的主体,主体就包含了消息名字和参数。

然后我有一个大胆的构想,我既然能够进行这么一个转换操作的话,是不是意味着这个底层的等价表达式一样可以执行达到同样的效果呢?我来验证一下,直接把调用sayHello方法的底层代码拷贝出来,放到OC代码下面,然后运行。

一模一样的打印了Hello!所以我通过这种objc_msgSend的方式,也是一样能够实现OC的方法调用的。这里Runtime的三种方式就用了两种,还一种方式是什么呢?就是直接把这个sel_registerName("sayHello")

改成@selector(sayHello),这三种方式,同时去发起了Runtime,都具备运行时功能。然后我来加点难度,当前是给person对象发送消息,而person只实现sayHello方法,我再写一个LGTeacher,让LGPerson继承自LGTeacher,然后在LGTeacher中实现这个sayWorld方法。

现在按照正常的理解,我给普通的person对象发送一个sayWorld方法,就会调起父类LGTeacher的sayWorld方法,我来运行一下。

没有报错了,然后我再clang一下看看objc_msgSend是不是还是给person发的消息呢?结果依然是给person发的消息,并不是直接给person的父类。

那问题就来了,objc_msgSend是如何让消息传递到父类的呢?难道我要去看他的底层源码吗?先不管这么多,我先全局搜索一下试试。

搜到了一个比较有意思的东西,在objc_msgSend下面有一个objc_msgSendSuper,那这是什么意思呢?翻译过来是消息发送父类,难道可以直接给父类发消息?

我直接来试一下,如果我要用objc_msgSendSuper来sayWorld的话,怎么做呢,首先要明白他是什么样的结构,我把他写在OC代码里面,然后点击跳转到定义。

第一个参数是void*类型的一个结构体,另一个就是SEL,还有其他的一些参数,那第一个参数里面的objc_super又是什么呢?我也不知道,来全局搜索一下看能不能找到。果然就找到了一个。

他也很简单,只有三个参数,第二个参数有一个条件,如果满足__OBJC2__的话,那第二个参数就没有了,也就是说,这个结构体里面只有两个参数了,一个是receiver,一个是super_class,然后我来手动写一个这样的结构,来作为objc_msgSendSuper的第一个参数。

receiver就是接收者,给person对象,super_class按理说我直接给父类LGTeacher.class就可以了,然后把zs_objc_super作为第一个参数给objc_msgSendSuper,指针地址就是&zs_objc_super,剩下的是一个SEL,我就直接给一个@selector(sayWorld)。

报了个错,根据之前调用objc_msgSend的经验,也一样的需要强转一下。

直接运行,完美的执行了sayWorld,并且super_class也可以给LGPerson.class,也是一样的,无非就是多找一层和少找一层的区别。

未完待续...

- END -

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HelloCoder全栈小集 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档