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,我们需要导入头文件
看下打印结果
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() 发送完毕。
下篇文章继续讲解快速查找和消息转发~~