编译时和运行时
编译时
顾名思义就是正在编译的时候 .
那什么叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码
.(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.)
编译时就是简单的作一些翻译工作 ,比如检查老兄你有没有粗心写错啥关键字了啊。有词法分析
,语法分析
之类的过程.。就像个老师检查学生的作文中有没有错别字和病句一样 .如果发现啥错误编译器就告诉你.如果你用微软的VS的话,点下build。那就开始编译,如果下面有errors或者warning信息,那都是编译器检查出来的。所谓这时的错误就叫编译时错误,这个过程中做的啥类型检查也就叫编译时类型检查,或静态类型检查(所谓静态嘛就是没把真把代码放内存中运行起来, 而只是把代码当作文本来扫描下).。所以有时一些人说编译时还分配内存啥的肯定是错误的说法。
运行时
就是代码跑起来了。被装载到内存中去了 。(你的代码保存在磁盘上没装入内存之前是个死家伙。只有跑到内存中才变成活的)。而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样。不是简单的扫描代码.而是在内存中做些操作,做些判断。)
Runtime版本
Runtime有两个版本 一个Legacy版本(早期版本) ,一个Modern版本(现行版本**)
***** 早期版本对应的编程接口:Objective-C 1.0
***** 现行版本对应的编程接口:Objective-C 2.0
***** 早期版本用于Objective-C 1.0, 32位的Mac OS X的平台上
***** 现行版本:iPhone程序和Mac OS X v10.5 及以后的系统中的 64 位程序
Runtime调用的三种方式
-
Objective-C
方式,[penson sayHello]
-
Framework & Serivce
方式,isKindOfClass
-
Runtime API
方式,class_getInstanceSize
int main(int argc, const char * argv[]) {
@autoreleasepool {
ELPerson *person = [ELPerson alloc];
[person sayNB];
}
return 0;
}
我们转换成c++代码,看一下底层的实现
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
ELPerson *person = ((ELPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ELPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
return 0;
}
发现底层调用了objc_msgSend()
方法,所以方法的本质
就是消息发送
那么有了objc_msgSend()
我们可不可以直接调用发送消息呢
int main(int argc, const char * argv[]) {
@autoreleasepool {
ELPerson *person = [ELPerson alloc];
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
NSLog(@"Hello, World!");
}
return 0;
}
2021-06-28 19:26:58.975392+0800 [11456:2734467] 666
2021-06-28 19:26:58.975763+0800 [11456:2734467] Hello, World!
由此可见,直接调用objc_msgSend()
发送消息也是可以的
探索objc_msgSend实现
寻找objc_msgSend实现入口
我们打开源码之后,搜索objc_msgSend
,找到arm64架构下面的代码,再寻找ENTRY,就是实现部分了。如下:
1、.s文件的后缀代表汇编实现,objc_msgSend
非常常用,用汇编实现速度会更快;
2、汇编的实现入口就是ENTRY,结束标志END_ENTRY
3、objc_msgSend
在不同架构下面都有相应的实现,我们这边主要看真机下面的实现(以arm64结尾的)
objc_msgSend arm64下基本实现
1、比较p0,是否为空。p0也就是消息接收者
2、判断是否支持TAGGED_POINTERS
是,返回LNilOrTagged,否,返回LReturnZero
3、通过对象isa
获取class
存p16
4、获取完LGetIsaDone
走CacheLookup
流程
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // 判断p0是否为空,p0就是消息接受者
#if SUPPORT_TAGGED_POINTERS //__LP64__支持TAGGED_POINTERS类型
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone://符号标记,拿到isa之后继续后面操作
// 调用CacheLookup传递三个参数NORMAL, _objc_msgSend, __objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
\#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
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
5、通过内存平移获取得到p11
6、通过掩码运算获取到bucketsand p10, p11, #0x0000fffffffffffe
7、通过逻辑右移计算哈希函数得到_cmd
& mask
8、通过bucket结构体得到(imp,sel)= *bucket
9、判断查询的sel和_cmd是否相等
9.1、通过向前查找的方式递归查找{imp, sel} = *bucket--
9.2、相等的时候命中CacheHit
9.3、最后还是找不到MissLabelDynamicif (sel == 0) goto Miss;
//参数拿过来 NORMAL, _objc_msgSend, __objc_msgSend_uncached,MissLabelConstant
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//x16的值放入x15
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
\#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS//模拟器或者macOS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16//arm64真机
//#define CACHE (2 * __SIZEOF_POINTER__)__SIZEOF_POINTER__=指针大小=8
//[x16, #CACHE] = isa+ 0x10 = _bucketsAndMaybeMask的内存地址//然后放入到p11中
ldr p11, [x16, #CACHE] // p11 = mask|buckets
//arm64下面是1
\#if CONFIG_USE_PREOPT_CACHES
//A12以后的芯片
\#if __has_feature(ptrauth_calls)
// 63..60: hash_mask_shift
// 59..55: hash_shift
// 54.. 1: buckets ptr + auth
// 0: always 1
//测试位不为0时,跳转
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
\#else
//p11 & #0x0000fffffffffffe = buckets //获取第1位到第47位
// 63..53: hash_mask
// 52..48: hash_shift
// 47.. 1: buckets ptr
// 0: always 1
and p10, p11, #0x0000fffffffffffe // p10 = buckets
//测试位不为0时,跳转
tbnz p11, #0, LLookupPreopt\Function
\#endif
// p12 = p1 ^(p1 >> 7) = _cmd ^ (_cmd >> 7)
eor p12, p1, p1, LSR #7
//p11 >> 48 = mask
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
\#else
//p11 = caceh & #0x0000ffffffffffff = buckets = p10
and p10, p11, #0x0000ffffffffffff // p10 = buckets
//p11 >> 48 = mask & p1(sel) = p12(index)
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
\#endif // CONFIG_USE_PREOPT_CACHES
\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
\#else
\#error Unsupported cache mask storage for ARM64.
\#endif
//p10 = buckets
//mask & p1(sel) = p12(index)
//PTRSHIFT = 3
//mask & _cmd << 4 相当于地址转下标---->相当于b[i]= b 平移i个单位
//p13就是当前要查找的bucket
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
//[x13]=*bucket--
//取到bucket里面的imp和sel
//分别存到p17和p9里面
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
//比较sel 和传进来的_cmd
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
//缓存命中
2: CacheHit \Mode // hit: call or return imp
// }
//判断p9是否为空->MissLabelDynamic
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
\#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
\#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
\#else
\#error Unsupported cache mask storage for ARM64.
\#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
\#if CONFIG_USE_PREOPT_CACHES
\#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
\#error config unsupported
\#endif
LLookupPreopt\Function:
\#if __has_feature(ptrauth_calls)
and p10, p11, #0x007ffffffffffffe // p10 = buckets
autdb x10, x16 // auth as early as possible
\#endif
// x12 = (_cmd - first_shared_cache_sel)
adrp x9, _MagicSelRef@PAGE
ldr p9, [x9, _MagicSelRef@PAGEOFF]
sub p12, p1, p9
// w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
\#if __has_feature(ptrauth_calls)
// bits 63..60 of x11 are the number of bits in hash_mask
// bits 59..55 of x11 is hash_shift
lsr x17, x11, #55 // w17 = (hash_shift, ...)
lsr w9, w12, w17 // >>= shift
lsr x17, x11, #60 // w17 = mask_bits
mov x11, #0x7fff
lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
and x9, x9, x11 // &= mask
\#else
// bits 63..53 of x11 is hash_mask
// bits 52..48 of x11 is hash_shift
lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
lsr w9, w12, w17 // >>= shift
and x9, x9, x11, LSR #53 // &= mask
\#endif
ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
cmp x12, w17, uxtw
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
.if \Mode == NORMAL
br x17
.elseif \Mode == LOOKUP
orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
add x16, x16, x9 // compute the fallback isa
b LLookupStart\Function // lookup again with a new isa
.endif
\#endif // CONFIG_USE_PREOPT_CACHES
.endmacro