逐行剖析objc_msgSend汇编源码

前言

本文会带领大家分析ARM64架构下objc_msgSend汇编部分的代码。在此之前希望你能够对汇编有一些简单的理解。当然这并不是说你需要记住arm指令集的各种指令以及用法,每一条指令的用途都会在第一次出现的时候给予说明,了解计算机底层的操作以及它如何存取使用数据才是我们关心的重点。


简述

每一个Objective-C的对象都属于一个类,每一个Objective-C的类都会有一个方法列表,而每一个方法都包含有一个selector,一个指向函数具体实现的函数指针(IMP) 和一些元数据。对于objc_msgSend来说它的任务就是传递对象和selector,寻找相关方法的函数指针,然后跳转到对应的函数指针处。

方法的寻找过程非常的复杂。如果一个方法没有在类的方法列表中被发现,那么程序需要从该类的父类里去查寻方法。如果找到根类还没有找到对应的方法,那么runtime的消息转发就会被触发。这里需要提醒的是,当一个类第一次接收到消息的时候,这个类会调用自身的+initialize方法。

因为每一次方法的调用都需要去查寻,所以需要一个非常高效的查寻来满足日常场景下的使用。但是很显然,复杂麻烦的方法查寻流程很难满足这样一个需求。

Objective-C给出的解决方案是方法缓存。每一个类会有一个缓存,用哈希表的组织形式存储一些selector和函数指针(也就是Objective-C里的IMP)。这样的存储方式似的查寻效率非常的高效。在查寻一个方法的时候,runtime会先去缓存中查寻,如果没有命中缓存,就去走复杂完整的方法查寻流程,查找成功以后替换缓存中的内容。下次调用该方法查寻速度就非常的快了。

objc_msgSend是用汇编实现的。这样做主要是有两个原因。一方面是因为在C语言中,在一个函数里保护未知的参数,并且跳转到任一的函数指针不太可能实现。C语言并不含括实现这些需求的一些必要特性。另一方面是因为对于objc_msgSend而言,效率非常的重要,所以用汇编这样一个非常靠近底层的语言,让objc_msgSend能够运行的尽可能的快一点。

当然,没有人会愿意用汇编来实现整个复杂的方法查寻流程,这样做也确实没有多大的意义。对于复杂的查寻流程而言,编程语言效率上节省的时间对于整个流程来说是微乎其微的。消息发送这一部分的代码可以被分为两块。其一是快速查寻,也就是用汇编实现的objc_msgSend;另一个较慢的查寻方式是用C实现的。汇编的部分负责去在缓存中查寻方法,如果命中缓存就跳转到对应的IMP。否则调用C的代码去处理接下来的业务逻辑。

总结一下,objc_msgSend主要负责的有:

  1. 获取传入对象的类
  2. 获取类的方法缓存
  3. 通过selector在缓存中寻找方法
  4. 如果没有命中缓存,调用C实现的代码来继续查寻
  5. 命中缓存则跳到方法对应的IMP

接下来让我们看看objc_msgSend具体是如何实现的。


逐条指令分析

objc_msgSend会根据不同的情况给出不同的处理方法。一些消息为nil,指针类型是tagged pointers或者哈希表冲突的情况,会有对应的特殊代码来处理。这篇文章会从最普通的情况开始入手,以一个非空,非tagged pointers,查寻的方法命中缓存不再需要扫描这样一个线性的流程来展开描述。如果遇到分支会先略过,等到整个主体的流程走完以后,再回头分析这些分支的情况。

后续的文章会将指令逐条依次列出,但是部分相关的指令会放在一起展示。指令的后面会给出解释,这些指令做了什么,为什么这么做。不需要太过纠结指令本身的含义,请把注意力放在每一条指令的讨论上。

每一条指令的偏移量是根据函数的始地址计算出来的。这类似于一个计数器,标识出每一条指令。

ARM64架构的处理器有31个64bit的整数寄存器,分别被标记为x0 - x30。每一个寄存器也可以分离开来只使用低32bit,标记为w0 - w30。其中x0 - x7是用来传递函数的参数的。这意味着objc_msgSend接收的self参数放在x0上,_cmd参数放在x1上。

每一个方法调用都会有两个默认参数分别是self和_cmd

 0x0000 cmp     x0, #0x0
 0x0004 b.le    0x6c
复制代码

这条指令是将self和0做了一个比较。如果self的值小于等于0那么会跳转到0x6c继续处理。0代表的是nil。这是对发送消息的对象是否为nil的判断和处理。这条指令同样可以处理self是tagged pointers的情况。ARM64架构里tagged pointers会设置高位bit来标识自己(有趣的是在x86-64架构里设置的是低位bit)。高位bit被设置的情况,self会被解析成一个有符号的整数,它的值是负数。对于一般的情况,self作为一个普通的指针,这个分支是不会被触发的。


0x0008 ldr    x13, [x0]
复制代码
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值