OC是一门动态语言所有的方法都是由运行时进行。 Objective-C 语言将决定尽可能的从编译和链接时推迟到运行时。只要有可能,Objective-C 总是使用动态 的方式来解决问题。这意味着 Objective-C 语言不仅需要一个编译器,同时也需要一个运行时系统来执行 编译好的代码。这儿的运行时系统扮演的角色类似于 Objective-C 语言的操作系统,Objective-C 基于该系统来工作。 Runtime的作用是 能动态产生/修改一个类,一个成员变量,一个方法
Runtime调用有三种方式
- NSObject(peformselector)
- Selector(底层会转换为objc_msgSend())
- Runtime的Api
objc_msg_send()
我们知道OC的函数调用是消息发送机制,那么消息发送机制是如何实现的呢。
Animals * animal = [[Animals alloc]init];
[animal eat];
复制代码
将该文件编译成c++文件通过
clang-rewrite-objc 文件名 -o test.c++
命令 一共9w多行代码只需看最后
// -(void) eat;
/* @end */
#pragma clang assume_nonnull end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_b2_hs7ds2bd5zz7d752kk495bhw0000gn_T_main_f668c6_mi_0);
Animals * animal = ((Animals *(*)(id, SEL))(void *)objc_msgSend)((id)((Animals *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Animals"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)animal, sel_registerName("eat"));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
复制代码
objc_msgSend(void /* id self, SEL op, ... */ ) 当类初始化时候 显示获取 id self,即 (id)objc_getClass("Animals"),就是根据类名取获取这个类,然后alloc,init就是 #selector(alloc) 其底层实现是 sel_registerName("alloc/init"),其目的就是为了查找该类里面有没该方法 第二句同理target是已经生产的animal selector是 eat方法 sel_registerName("eat")去类的内存布局中查找eat方法
objc_msgsend 底层实现有两种方法一中是快速查找一种是慢速查找 快速是通过汇编从响应的缓存里面找到,慢速是通过c,c++以及汇编一起完成的。
之所以使用汇编的原因是 :
- c里面不会写一个函数保留未知的参数跳转到任意的指> 针,c无法实现,汇编可以通过寄存器直接实现
- 快,下层编译
快速查找
快速查找直接通过 汇编 + 缓存 来进行查找的 缓存是来自于类、
///ps: 类继承于对象从这里也可以看出来类其实也是一个对象
struct objc_class: objc_objcet {
// class ISA;
Class superclass;
cache_t cache; ///
classs_data_bits_t bitgs; /// 类里面所有的数据
class_rw_t *data() {
return bits.data()
}
}
复制代码
类结构里的 cacle_t 缓存 存储方法的Selector(在iOS中SEL就是可以根据一个SEL选择对应的方法IMP。SEL只是描述了一个方法的格式)和IMP(一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。)。IMP和Selector会组成一张哈希表,通过哈希直接查找非常快,当查找第一个方法的时候第一步找到cache,如果里面有他会直接返回。如果没有会经历一个复杂的过程(慢速查找)。找到了会在里面存一份方便下次进行查找,这次主要介绍快速找找的过程通过OC源码
刚刚的方法通过Xcode调试调试汇编页面
在源码里搜索_objc_msgsend
先把完整的汇编源码贴上,可以往下看,然后在回来看
********************************************************************
*
* 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
*
********************************************************************
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
ENTRY _objc_msgSend ///************************************** 1.进入objcmsgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
/// x0 recevier
// 消息接收者 消息名称
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative) ****************************************************2.isa 优化
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone: ///**************************************************** 3.isa优化完成
CacheLookup NORMAL // calls imp or objc_msgSend_uncached ///*******************************************4.执行 CacheLookup NORMAL
LNilOrTagged:
b.eq LReturnZero // nil check
/// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
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]
b LGetIsaDone
LExtTag:
// 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
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret
END_ENTRY _objc_msgSend
ENTRY _objc_msgLookup
UNWIND _objc_msgLookup, NoFrame
cmp x0, #0 // nil check and tagged pointer check
b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LLookup_GetIsaDone:
CacheLookup LOOKUP // returns imp
LLookup_NilOrTagged:
b.eq LLookup_Nil // nil check
/// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LLookup_ExtTag
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]
b 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
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
MESSENGER_START
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper
// no _objc_msgLookupSuper
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
ENTRY _objc_msgLookupSuper2
UNWIND _objc_msgLookupSuper2, NoFrame
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup LOOKUP
END_ENTRY _objc_msgLookupSuper2
.macro MethodTableLookup
// push frame
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)]
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3/// *********************************************6.方法为_class_lookupMethodAndLoadCache3调用的汇编语言
// 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
.endmacro
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup /// ********************************************** 5.查找IMP
br 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 x16 is the class to search
MethodTableLookup
ret
END_ENTRY __objc_msgLookup_uncached
STATIC_ENTRY _cache_getImp
and x16, x0, #ISA_MASK
CacheLookup GETIMP
LGetImpMiss:
mov x0, #0
ret
END_ENTRY _cache_getImp
复制代码
- LLookup_NilOrTagged///针对内存里寄存器进行赋值处理isa优化。
- LGetIsaDone isa /// isa处理完毕
- CacheLookup Normal 调用当前imp或者发送objcmsgsend_uncache
1.CacheLookup Normal
先贴源码
/********************************************************************
*
* CacheLookup NORMAL|GETIMP|LOOKUP
*
* Locate the implementation for a selector in a class method cache.
*
* Takes:
* x1 = selector
* x16 = class to be searched
*
* Kills:
* x9,x10,x11,x12, x17
*
* On exit: (found) calls or returns IMP
* with x16 = class, x17 = IMP
* (not found) jumps to LCacheMiss
*
********************************************************************/
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.macro CacheHit /// cachehit
.if $0 == NORMAL /// normal ///call imp
MESSENGER_END_FAST
br x17 // call imp
.elseif $0 == GETIMP
mov x0, x17 // return imp
ret
.elseif $0 == LOOKUP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz x9, LGetImpMiss
.elseif $0 == NORMAL
cbz x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro CacheLookup
// x1 = SEL, x16 = isa
ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
and w12, w1, w11 // x12 = _cmd & mask
add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4)
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b /// loop
3: // wrap: x12 = first bucket, w11 = mask
add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b /// loop
3: // double wrap
JumpMiss $0
.endmacro
复制代码
CacheLookup 有三种 NORMAL GETIMP LOOKUP
- CacheHit call imp /// 查找缓存
- CheckMiss - /// 找不到的处理 发送_objc_msgSend_uncached
- add /// 如果缓存里没有找到但是其他地方找到了这时候就可以add到缓存里面去
CacheHit 也是一个宏,如果$0 == Normal 则进行call imp 操作这是找到了操作。如果找不到的话,则执行check miss,check miss也是一个宏 $0 == Normal 会发送 objcmsgsend_uncache,这个时候整个流程就出来了。 CacheHit的意义就是要么查找IMP要么发送objcmsgsenduncache方法
2. _objc_msgSend_uncache
如果走到这里说明CacheHit并没有找到对应的方法而执行了_objc_msgSend_uncache /// 没有缓存去慢速查找imp
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup /// 重点 方法列表
br x17 // call imp
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 x16 is the class to search
MethodTableLookup
ret
END_ENTRY __objc_msgLookup_uncached
复制代码
MethodTableLookup 方法列表这个方法是关键, 因为 br x 17 是设置imp,而 MethodTableLookup 在之前调用说明他是在慢速查找。
3. MethodTableLookup
.macro MethodTableLookup
// push frame
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)]
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3 /// 重点查找IMP
// 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
.endmacro
复制代码
__class_lookupMethodAndLoadCache3 这个方法顾名思义是 查找方法列表并缓存,到了这里了我们发现源码里面并没有看到这个方法的定义。因为
__class_lookupMethodAndLoadCache3 方法为_class_lookupMethodAndLoadCache3调用的汇编语言 通过_class_lookupMethodAndLoadCache3 来到c++文件
快速查找总结
oc的方法调用本质是进行objc _ msgSend调用,而objcmsgSend进行实现的时候有两种方式一种是快速查找一种是慢速查找。快速查找是oc先去类结构里的cache_ t的类面去查找,里面是由 c c++ 和汇编一起完成的,采用会变得原因是他可以做到c语言无法完成的原因是c里面不会写一个函数保留未知的参数跳转到任意的指针,c无法实现,汇编可以通过寄存器直接保留,而且速度快,进入objc_ msg_ send的时候
- 首先会执行LLookup_NilOrTagged对isa进行优化,优化完毕后会执行LLookup_GetIsaDone.
- 之后执行CacheLookup进行缓存查找,缓存查找会分三步CacheHit如果是Normal,则直接返回imp说明缓存中有并返回(br x17),如果是GETIMP,LOOKUP,则会继续往下走。如果没有的话则执行checkmiss,Normal LOOKUP操作 发送objc_msgSend_uncahced消息,objc_msgSend_uncahced进入该方法后说明没有缓存进入慢速查找IMP,里面有一个MethodTableLookup,之所以这句是关键代码适应br x 17是为imp进行设置,所以该代码是关键,这方法里面执行**__class_lookupMethodAndLoadCache3**,因为该方法是**_class_lookupMethodAndLoadCache3**调用的汇编语言所以就查找到这个方法,该方法是c++文件里的代码
到了这里就已经进入到c++ 文件里面。下篇文章具体分析慢速查找流程。
ps:以上为个人理解,如果有误欢迎指正。一起进步