objc_msgSend 流程

1.runtime简介

Runtime 分为两个版本,legacy 和 modern,分别对应 Objective-C 1.0 和 Objective-C 2.0。我们通常只需要专注于modern

通过c、c++、混编给我们的object-c提供运行时功能

调用runtime有三种途径

(1)Objective-C Code

          例:[person sayNB] 

(2)Framework&Serivce

          例: isKindofClass

(3)Runtime API 

          例:class_getInstanceSize

2.探索方法本质

1.clang


通过clang -rewrite-objc main.m -o main.cpp将main.m编译成 c++文件


我们发现都是通过底层函数来发送消息的 ,当然我们也可以尝试直接调用底层方法。

注意:如果要在工程当中直接使用 objc_msgSend API,我们需要导入头文件  和 将 Enbale Strict Checking of objc_msgSend Calls 设置为 NO,这样才不会报错。

看下打印结果


2.发送消息的两种方式

objc_msgSend  上面已经用过了

 objc_msgSendSuper 调用之前 我们在建一个类


如果我们用[person sayHello]也就是说消息的接受者还是自己 但是方法在父类 这时候就用到objc_msgSendSuper


打印结果 正常

主要也就是研究sel->imp

3.探索 objc_msgSend流程

objc_msgSend() 是用 汇编语言 实现的。之所以使用汇编实现,一方面消息发送的过程需要足够的快速,高级语言在执行的时候都是需要翻译成汇编语言,经过编译成被机器识别的二进制文件,使用汇编可以省去这一翻译过程,可以更快速被机器识别;且对于消息的发送,存在很多未知的参数,这有很多不确定性,使用汇编的寄存器要比 C 或者 C++ 表现好的多。

源码查看:

/********************************************************************

 *

 * id objc_msgSend(id self, SEL _cmd, ...);

 * IMP objc_msgLookup(id self, SEL _cmd, ...);

 * 

 * objc_msgLookup ABI:

 * IMP returned in x17

 * x16 reserved for our use but not used

 *

 ********************************************************************/

#if SUPPORT_TAGGED_POINTERS

.data

.align3

.globl _objc_debug_taggedpointer_classes

_objc_debug_taggedpointer_classes:

.fill16,8,0

.globl _objc_debug_taggedpointer_ext_classes

_objc_debug_taggedpointer_ext_classes:

.fill256,8,0

#endif

ENTRY _objc_msgSend

UNWIND _objc_msgSend, NoFrame

cmp p0,#0 // nil check and tagged pointer check

#if SUPPORT_TAGGED_POINTERS

b.le LNilOrTagged //  (MSB tagged pointer looks negative)

#else

b.eq LReturnZero

#endif

ldr p13, [x0] // p13 = isa

GetClassFromIsa_p16 p13 // p16 = class

LGetIsaDone:

// calls imp or objc_msgSend_uncached

CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS

LNilOrTagged:

b.eq LReturnZero // nil check

// tagged

adrp x10, _objc_debug_taggedpointer_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF

ubfx x11, x0,#60, #4

ldr x16, [x10, x11, LSL#3]

adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE

add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF

cmp x10, x16

b.ne LGetIsaDone

// ext tagged

adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF

ubfx x11, x0,#52, #8

ldr x16, [x10, x11, LSL#3]

b LGetIsaDone

// SUPPORT_TAGGED_POINTERS

#endif

LReturnZero:

// x0 is already zero

mov x1,#0

movi d0,#0

movi d1,#0

movi d2,#0

movi d3,#0

ret

END_ENTRY _objc_msgSend

ENTRY _objc_msgLookup

UNWIND _objc_msgLookup, NoFrame

cmp p0,#0 // nil check and tagged pointer check

#if SUPPORT_TAGGED_POINTERS

b.le LLookup_NilOrTagged //  (MSB tagged pointer looks negative)

#else

b.eq LLookup_Nil

#endif

ldr p13, [x0] // p13 = isa

GetClassFromIsa_p16 p13 // p16 = class

LLookup_GetIsaDone:

// returns imp

CacheLookup LOOKUP, _objc_msgLookup

#if SUPPORT_TAGGED_POINTERS

LLookup_NilOrTagged:

b.eq LLookup_Nil // nil check

// tagged

adrp x10, _objc_debug_taggedpointer_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF

ubfx x11, x0,#60, #4

ldr x16, [x10, x11, LSL#3]

adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE

add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF

cmp x10, x16

b.ne LLookup_GetIsaDone

LLookup_ExtTag:

adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE

add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF

ubfx x11, x0,#52, #8

ldr x16, [x10, x11, LSL#3]

b LLookup_GetIsaDone

// SUPPORT_TAGGED_POINTERS

#endif

LLookup_Nil:

adrp x17, __objc_msgNil@PAGE

add x17, x17, __objc_msgNil@PAGEOFF

ret

END_ENTRY _objc_msgLookup

STATIC_ENTRY __objc_msgNil

// x0 is already zero

mov x1,#0

movi d0,#0

movi d1,#0

movi d2,#0

movi d3,#0

ret

END_ENTRY __objc_msgNil

ENTRY _objc_msgSendSuper

UNWIND _objc_msgSendSuper, NoFrame

ldp p0, p16, [x0] // p0 = real receiver, p16 = class

// calls imp or objc_msgSend_uncached

CacheLookup NORMAL, _objc_msgSendSuper

END_ENTRY _objc_msgSendSuper

// no _objc_msgLookupSuper

ENTRY _objc_msgSendSuper2

UNWIND _objc_msgSendSuper2, NoFrame

ldp p0, p16, [x0] // p0 = real receiver, p16 = class

ldr p16, [x16,#SUPERCLASS] // p16 = class->superclass

CacheLookup NORMAL, _objc_msgSendSuper2

END_ENTRY _objc_msgSendSuper2

ENTRY _objc_msgLookupSuper2

UNWIND _objc_msgLookupSuper2, NoFrame

ldp p0, p16, [x0] // p0 = real receiver, p16 = class

ldr p16, [x16,#SUPERCLASS] // p16 = class->superclass

CacheLookup LOOKUP, _objc_msgLookupSuper2

END_ENTRY _objc_msgLookupSuper2

.macro MethodTableLookup

// push frame

SignLR

stp fp, lr, [sp,#-16]!

mov fp, sp

// save parameter registers: x0..x8, q0..q7

sub sp, sp,#(10*8 + 8*16)

stp q0, q1, [sp,#(0*16)]

stp q2, q3, [sp,#(2*16)]

stp q4, q5, [sp,#(4*16)]

stp q6, q7, [sp,#(6*16)]

stp x0, x1, [sp,#(8*16+0*8)]

stp x2, x3, [sp,#(8*16+2*8)]

stp x4, x5, [sp,#(8*16+4*8)]

stp x6, x7, [sp,#(8*16+6*8)]

str x8,    [sp,#(8*16+8*8)]

// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)

// receiver and selector already in x0 and x1

mov x2, x16

mov x3,#3

bl _lookUpImpOrForward

// IMP in x0

mov x17, x0

// restore registers and return

ldp q0, q1, [sp,#(0*16)]

ldp q2, q3, [sp,#(2*16)]

ldp q4, q5, [sp,#(4*16)]

ldp q6, q7, [sp,#(6*16)]

ldp x0, x1, [sp,#(8*16+0*8)]

ldp x2, x3, [sp,#(8*16+2*8)]

ldp x4, x5, [sp,#(8*16+4*8)]

ldp x6, x7, [sp,#(8*16+6*8)]

ldr x8,    [sp,#(8*16+8*8)]

mov sp, fp

ldp fp, lr, [sp],#16

AuthenticateLR

.endmacro

STATIC_ENTRY __objc_msgSend_uncached

UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION

// Out-of-band p16 is the class to search

MethodTableLookup

TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached

STATIC_ENTRY __objc_msgLookup_uncached

UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION

// Out-of-band p16 is the class to search

MethodTableLookup

ret

END_ENTRY __objc_msgLookup_uncached

STATIC_ENTRY _cache_getImp

GetClassFromIsa_p16 p0

CacheLookup GETIMP, _cache_getImp

LGetImpMiss:

mov p0,#0

ret

END_ENTRY _cache_getImp

源码分析:

首先 ENTRY 进入  _objc_msgSend:

cmp 是比较函数, p0 是  _objc_msgSend 传入的第一个参数,也就是要响应方法的实例,一般称之为 “消息接收者” ,#0 代表 值 0,这一步就是判断 p0 是否为空。如果支持taggedpointer类型,进入 LNilOrTagged 否则进入 LReturnZero ,结束  _objc_msgSend 流程 。开始 一轮 向 nil 发送消息的流程。如果 p0 不为空,向下执行


读取 x0 的首地址 存入 p13。x0 为第一个参数,依然是 消息接收者,不管它是类还是对象,它的第一个成员都是 isa, 所以取x0的首地址,即为 isa, 将 isa 存入 p13 。这里取 isa 的目的是因为 ,不管是对象方法还是类方法,我们都可以通过 isa 的指向 在类或元类的缓存或方法列表中去查找。所以接下来就要通过 isa 取到类或元类。


GetClassFromIsa_p16就是获取类或元类的过程


CacheLookup  从缓存获取imp流程

先看下CacheLookup源码:

首先在类的结构分析中,我们已知 类的结构排布 为 isa、superclass、cache_t、class_data_bits,这里我们要进行的是查找缓存的流程,缓存的信息是存储在 cache_t 中的。


x16 是我们上一步中获取到的 类信息,x16 偏移 16字节 就是取到 cache_t 结构,存入 p11 中

在 arm64架构 cache_t 的第一个成员为_maskAndBuckets ,所以这里的 p11 存储的就是 _maskAndBuckets 。

p11 为 _maskAndBuckets ,它的低48位存储 buckets , 高16位存储 mask,


将 p11 & #0x0000ffffffffffff,就是取出  _maskAndBuckets 中 的第0 - 47 字节,得到的就是 buckets 赋值给 p10 。

之后,将 p11 逻辑右移 48个字节,相当于只保留 mask 的值,与 p1(sel) 进行与运算之后,得到 sel & mask 存入 p12。这里的本质就是我们在写入内存遇到的 `cache_hash` 方法一模一样,目的就是拿到方法缓存的哈希下标。 在 cache_t 的结构中,mask 为 缓存表 总长度的值 - 1,这里的 sel & mask 得到的值 必定落在 buckets缓存表的长度 范围内,作为开始遍历查找缓存的 下标值。


获取完毕buckets、 mask、 _cmd & mask 的值之后,做了这样一个操作:p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) 。PTRSHIFT 的值在arm64 架构下的值为3 ,所以((_cmd & mask) << (1+PTRSHIFT))得到的结果为16的倍数,具体的倍数取决于 _cmd & mask 计算出的大小。buckets 中存储的是 sel 和 imp ,每一个 bucket 占用16字节,_cmd & mask 作为 buckets 中的初始遍历地址,这一步的操作 其实就是将 buckets 偏移到 初始查询位置,已取到初始查询的 bucket 存入 p12 中。 取到了 初始查询 bucket, 也就取到了 bucket 中的 sel 和 imp ,分别存入 p9 和 p17。 就是内存偏移取对应位置的bucket 把其中的imp和sel分别存入


接下来比较上一步获取到的 sel(p9 )是否与我们要查找的 sel (p1) 相同,如果相同 通过 CacheHit 将 imp 返回,如果不匹配,跳转到 2:

如果上面的结果不匹配,这里将 把比对的样本 进行更换 ,也就是进入循环,更换比较的bucket。p10 为 buckets散列表的首地址,p12 为当前对比的 bucket ,如果 p10 与 p12 相等,则意味着当前遍历到了 buckets 的首地址,此时执行3:

p12 = buckets + (mask << 1+PTRSHIFT) 和上面的指针偏移原理是一样的,只不过这次偏移的位置不同,因为mask 是总长度的值-1,所以此次操作是偏移到已存储的 buckets 中的最后一个bucket。

将当前遍历的位置,移到最后,因为遍历是从_cmd & mask的位置开始,通过 * --bucket 向前遍历,当到达第一个时,则将位置移到最后,继续向前遍历,以确保整个缓存表全部查找。

通过 将 buckets 的指针 偏移 获取到新的 bucket ,取到sel ,继续回到1:执行对比操作,命中则 CacheHit ,否则继续执行 2: 如果整个 buckets 遍历结束,依然没有得到匹配信息,则跳转到 CheckMiss


在 NORMAL 模式下,会进入 __objc_msgSend_uncached 流程。


__objc_msgSend_uncached 中最核心的逻辑就是 MethodTableLookup ,因为我们的缓存并没有命中,这里是开始去方法列表 (methodList) 的查找流程。


我们观察 MethodTableLookup 内容之后会定位到 _lookUpImpOrForward。真正的方法查找流程核心逻辑是位于 _lookUpImpOrForward 里面的。 至此,objc_msgSend() 的快速查找流程就结束了,接下来进入的 慢速查找流程,也就是通过 isa 与 superclass 的指向,一层层寻找下去,直到整个 objc_msgSend() 发送完毕。

下篇文章继续讲解快速查找和消息转发~~

你可能感兴趣的:(objc_msgSend 流程)