OC是一门动态语言全部的方法都是由运行时进行。 Objective-C 语言将决定尽量的从编译和连接时推迟到运行时。只要有可能,Objective-C 老是使用动态 的方式来解决问题。这意味着 Objective-C 语言不只须要一个编译器,同时也须要一个运行时系统来执行 编译好的代码。这儿的运行时系统扮演的角色相似于 Objective-C 语言的操做系统,Objective-C 基于该系统来工做。 Runtime的做用是 能动态产生/修改一个类,一个成员变量,一个方法c++
Runtime调用有三种方式缓存
咱们知道OC的函数调用是消息发送机制,那么消息发送机制是如何实现的呢。bash
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方法oop
objc_msgsend 底层实现有两种方法一中是快速查找一种是慢速查找 快速是经过汇编从响应的缓存里面找到,慢速是经过c,c++以及汇编一块儿完成的。布局
之因此使用汇编的缘由是 :post
- 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源码ui
刚刚的方法经过Xcode调试调试汇编页面spa
在源码里搜索_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
复制代码
先贴源码
/********************************************************************
*
* 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 也是一个宏,若是$0 == Normal 则进行call imp 操做这是找到了操做。若是找不到的话,则执行check miss,check miss也是一个宏 $0 == Normal 会发送 objcmsgsend_uncache,这个时候整个流程就出来了。 CacheHit的意义就是要么查找IMP要么发送objcmsgsenduncache方法
若是走到这里说明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 在以前调用说明他是在慢速查找。
.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的时候
到了这里就已经进入到c++ 文件里面。下篇文章具体分析慢速查找流程。
ps:以上为我的理解,若是有误欢迎指正。一块儿进步