1.runtime简介
- 编译时
顾名思义,编译时就是正在编译的时候,而编译,指的是将代码翻译成机器可以识别的代码。在编译时,检查到的错误叫做编译时错误,做的类型检查叫做编译时类型检查,也叫做静态类型检查,这种情况下,是计算机还没将代码运行到内存中,只是把代码以文本的形式进行扫描。 - 运行时
运行时,就是代码在内存中运行起来了。运行时与编译时类型检查不一样,不只是简单的扫描代码,而是在内存中做了操作,或者做了什么判断。
runtime就是以C/C++以及汇编编写封装的供OC提供运行时的API。
2.objc_msgSend方法快速查找流程
2.1 通过clang命令简单探索
在main.m文件中demo代码如下:
@interface ZCPerson : NSObject
- (void)sayNB;
@end
@implementation ZCPerson
- (void)sayNB{
NSLog(@"666");
}
@end
@interface ZCTeacher : ZCPerson
- (void)sayHello;
- (void)sayNB;
@end
@implementation ZCTeacher
- (void)sayHello{
NSLog(@"666");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
ZCPerson *p = [ZCPerson alloc];
[p sayNB];
ZCTeacher *t = [ZCTeacher alloc];
[t sayHello];
[t sayNB];
}
return 0;
}
找到当前main.m文件所在路径,cd到当前文件目录下,使用clang命令:
clang -rewrite-objc main.m -o main.cpp
这样,就在当前文件目录下得到了main.cpp文件,打开main.cpp文件,找到main.m中main函数实现,发现mian函数在底层被编译成如下代码:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3l_y1z24zqx5qv48b07nkgx49wr0000gn_T_main_93b105_mi_2);
ZCPerson *p = ((ZCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ZCPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayNB"));
ZCTeacher *t = ((ZCTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ZCTeacher"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)t, sel_registerName("sayHello"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)t, sel_registerName("sayNB"));
}
return 0;
}
通过底层编译代码说明,对象方法调用在底层会被编译成objc_msgSend
的方式发送消息,也就是说,我们可以在main.m中将sayNB
或者sayHello
方法改成objc_msgSend
。导入#import
框架,在build setting中将enable strict checking objc_msgSend Calls
中的Yes
改为NO
,如下图:
接着,将main.m中的函数改为:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
ZCPerson *p = [ZCPerson alloc];
// [p sayNB];
objc_msgSend(p, sel_registerName("sayNB"));
ZCTeacher *t = [ZCTeacher alloc];
// [t sayHello];
// [t sayNB];
objc_msgSend(t, sel_registerName("sayHello"));
}
return 0;
}
通过输出可以证实, [p sayNB];
等价于objc_msgSend(p, sel_registerName("sayNB"));
。OC在进行方法调用的时候,会找到一个方法编号sel
,通过sel绑定到函数指针地址imp
,再通过imp
找到函数实现的内容。我们知道,在imp
查找函数内容属于指针级别的查找,所以sel
绑定到imp
才是需要进行探索的内容。
2.2 sel查找imp流程
我们知道方法存在于类/元类中,而查找元类需要isa
,isa
存在于对象当中(不论是实例对象还是类对象),通过isa
找到类后,开始找cache_t
,看看cache_t
中是否有缓存的方法,如果没有,则是从bit
里查找methodlist
,看看方法列表里是否有查找的方法。
- 源码分析
打开源码搜索objc_msgSend
,因为是要找汇编代码,所以点击搜索栏下objc-msg-arm64.s
文件里的ENTRY _objc_msgSend
进入代码:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check(判断当前消息发送对象是否为空)
/*
com :compare,比较
p0:objc_msgSend第一个参数,也就是当前发送消息的对象
#0:空
*/
#if SUPPORT_TAGGED_POINTERS //(判断是否支持TAGGEDPOINTER类型)
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa //拿出isa
GetClassFromIsa_p16 p13 // p16 = class //通过isa找到当前的类
LGetIsaDone: //isa流程查找完毕
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend //从缓存里获取imp的流程
- CacheLookup
#define CACHE (2 * __SIZEOF_POINTER__) //2*8 = 16
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE] //isa向右平移16位,拿到当前的cache_t // p11 = mask|buckets ,mask占高16位,buckets占48位
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets 拿到cache里的bucket
and p12, p1, p11, LSR #48 // x12 = _cmd & mask ,
/*LSR#48:逻辑右移48位拿到cache里的mask,也就是p11
p1:_cmd
*/
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)),获得当前的bucket
/**
PTRSHIFT: #define PTRSHIFT 3 // 1<sel != _cmd),判断缓存里的sel与当前方法的_cmd是否相等
b.ne 2f // scan more 不相等,走下面2: 流程
CacheHit $0 // call or return imp 相等则返回imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets 判断要查找的bucket是否是首地址
b.eq 3f //相同,则到3:流程处理 ,直接拿到最后一个bucket
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket 向前查找
b 1b // loop 没找到,就递归回去重新接着查找
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT),
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif