iOS 底层原理之objc_msgSend(上)

930 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,   点击查看活动详情

引言

上一篇中说到实例方法缓存在类的cache_t中,是通过提供的inset(sel, imp, cls)讲方法缓存起来,我们直接拦腰截断从插入方法开起来的,但是我们并不知道是谁引起的insert()也就没有办法形成闭环,下面要探索的就是何时出发的insert()。 首先在源码中insert打下断点,查看堆栈信息

image.png _objc_msgSend_uncached -> lookUpImpOrForward -> log_and_fill_cache -> insert,根据这个流程,可以这样理解么?"首次调用时方法没有找到,开始寻找方法,最终把方法插入到缓存中",这里是个问句,需要探索

调用方法底层实现

main.m文件的实现如下:

image.png 这里想知道[person saySomething]消息发送在底层做了什么,使用clang命令转换成c++,在main.m所在文件夹执行以下命令:

clang -rewrite-objc main.m -o main.cpp

执行完成后,会看到一个和main.m同级的main.cpp文件,打开并找到DXJPerson的实现

image.png 上图中可以发现:

  1. [person saySomething]在OC层面方法实现是没有带参数的,其实在底层是现实过程中带了两个隐藏参数DXJPerson * self, SEL _cmd
  2. [person saySomething]在底层是这样发送消息的
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));
简化后:
objc_msgSend(person, sel_registerName("saySomething"));

objc_msgSend()是消息发送的底层实现,那是不是我们也可以使用该api发送消息呢?答案是可以的,看下图

image.png

objc_msgSend()过程中,发现了另一个有意思的函数objc_msgSendSuper(),他是这样定义的,内部也有两个隐藏参数,一个是常见的sel,另一个是objc_super *类型的结构体指针

objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

打开objclib.A.dylib源码库,看下objc_super结构体内部实现有两个属性:

  • receiver : 消息的接受者
  • super_class : the first class to search(首先从哪个类开始被检索方法)

如下图: image.png

根据objc_msgSendSuper()的定义,使用该函数去发送一个消息:

image.png

objc_msgSend之汇编流程

到这里已经知道了消息的发送是通过objc_msgSend()实现,要想知道底层是实现,首先通过汇编跟流程,在[person saySomething]打下断点,然后选择Debug->Debug workflow ->Always show Disassembly(这里有打断点的几种方式)

image.png

objc_msgSend是属于libobjc.A.dylib库,打开该源码库,全局搜索objc_msgSend方法,因为该函数的底层是用汇编实现的所以只用找.s文件即可

image.png 下面分析一下汇编中objc_msgSend的实现

image.png

  1. cmp p0, #0, 根据源码提供的解释,p0是接受者的isa,拿 p0#0比较,是检测接受者是否是nil或者 tagged pointer类型
  2. ldr p13, [x0]x0 receive的isa地址,将[x0] 地址移入到p13
  3. GetClassFromIsa_p16 p13, 1, x0, 下图是GetClassFromIsa_p16的实现,红框中表示的是不同架构类型下的操作,想要做的事情是一样的,所以这里只拿一种做分析else流程 image.png ExtractISA p16, \src, \auth_address,src就是传递过来isa,根据下方源码的实现,将isa & ISA_MASK 赋值给p16,至此类的地址获取到了,放在p16
    .macro ExtractISA
    and $0, $1, #ISA_MASK // 将($1 & ISA_MASK)然后赋值给$0,p16 = $0 , \src = $1, \auth_address = $2
    
  4. CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached至此开始CacheLookup流程