一,runtime简介
-
参考官方
-
runtime简介
Runtime通常叫它运行时,还有一个大家常说的编译时,它们之间的区别是什么
-
编译时:顾名思义正在编译的时候,啥叫编译呢?就是编译器把源代码翻译成机器能够识别的代码。编译时会进行词法分析,语法分析主要是检查代码是否符合苹果的规范,这个检查的过程通常叫做静态类型检查 -
运行时:代码跑起来,被装装载到内存中。运行时检查错误和编译时检查错误不一样,不是简单的代码扫描,而是在内存中做操作和判断 -
runtiem版本
Runtime有两个版本,一个Legacy版本(早期版本),一个Modern版本(现行版本)
-
早期版本对应的编程接口:
Objective-C 1.0 -
现行版本对应的编程接口:
Objective-C 2.0,源码中经常看到的OBJC2 -
早期版本用于
Objective-C 1.0,32位的Mac OS X的平台 -
现行版本用于
Objective-C 2.0,iPhone程序和Mac OS X v10.5及以后的系统中的64位程序 -
runtime三种调用方式
Objective-C方式,[penson say]Framework & Serivce方式,isKindOfClassRuntime API方式,class_getInstanceSize
- 调用层级分布图如下
-
代码查看runtime
#import <Cocoa/Cocoa.h>
#import <objc/message.h>
@interface NBPerson : NSObject
- (void)sayNB;
- (void)sayHello;
@end
@implementation NBPerson
- (void)sayNB{
NSLog(@"NB---");
}
- (void)sayHello{
NSLog(@"Hello---");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NBPerson * nb = [NBPerson alloc];
[nb sayNB];
[nb sayHello];
NSLog(@"Hello, World!");
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}
- 通过
Clang生成.cpp文件。在.cpp文件中找到main函数,查看它底层代码。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NBPerson * nb = ((NBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NBPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)nb, sel_registerName("sayNB"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)nb, sel_registerName("sayHello"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0g_2qt7mmyn00l8xrhbw4p0rjyc0000gn_T_main_c966b1_mi_2);
}
return NSApplicationMain(argc, argv);
}
- 观察源码发现
- 在底层代码中,编译之后上层的代码都会得到有个解释。
- 调用方法的过程(消息的发送),objc_msgSend(消息的接收者,消息的主体(SEL+主体))
-
调用底层api
- 直接调用如下代码发现编译出错
Too many arguments to function call, expected 0, have 2
NBPerson * nb = [NBPerson alloc];
objc_msgSend(nb,sel_registerName("sayNB"));
- 设置
Build Settings->Enable Strict Checking of objc_msgSend Calls为NO,输出结果。
2021-08-08 14:39:10.378065+0800 objc_msgSend探索[2714:31870] NB---
- 通过
objc_msgSend和[perosn sayNB]结果是一样的,同时也验证了方法的本质是消息发送。在用objc_msgSend方式发送消息。
调用父类方法
- 新建
NBTeacher继承NBPerson
@interface NBTeacher : NBPerson
@end
@implementation NBTeacher
@end
- 方法调用
NBTeacher *tc = [NBTeacher alloc];
[tc sayNB];
- Clang生成.cpp文件
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NBTeacher *tc = ((NBTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NBTeacher"), sel_registerName("alloc"));
//子类能够调用父类方法
((void (*)(id, SEL))(void *)objc_msgSend)((id)tc, sel_registerName("sayNB"));
}
return NSApplicationMain(argc, argv);
}
- 通过源码我们发现
void objc_moveWeak(void) {}
//这两代码挨着的
void objc_msgSend(void) {}
void objc_msgSendSuper(void) {}
void objc_msgSendSuper_stret(void) {}
void objc_msgSend_fpret(void) {}
void objc_msgSend_stret(void) {}
我们先来看看 objc_msgSendSuper() 的声明。
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
发现 objc_msgSendSuper() 声明需要的参数 struct objc_super * _Nonnull super 和 SEL _Nonnull op,还有一些相应参数。SEL 我们知道,objc_super 是什么?我们不知道。接下来我们就一起看看 objc_super 是什么。全局搜索 objc_super 。
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
- 可以看出
objc_super里面有两个参数:receiver和super_class。接下来我们就调用一下objc_msgSendSuper
NBTeacher *tc = [NBTeacher alloc];
// [tc sayNB];
struct objc_super tcd_objc_super;
tcd_objc_super.receiver = tc;
tcd_objc_super.super_class = NBPerson.class;
objc_msgSendSuper(&tcd_objc_super, @selector(sayNB));
//输出结果
2021-08-08 15:34:08.709850+0800 objc_msgSend探索[3042:49060] NB---
二,objc_msgSend分析
- 全局搜索objc_msgSend
command+单击收起收起所有代码,然后找到.s文件
- 接下来我们就看
objc-msg-arm64.s文件,定位到ENTRY _objc_msgSend(进入到 objc_msgSend)。
- 分析源码
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// p0代表消息接受者的地址,这里判断消息接收者是否存在
cmp p0, #0 // nil check and tagged pointer check
// 判断是否支持Taggedpointer类型
#if SUPPORT_TAGGED_POINTERS
// 如果支持Taggedpointer类型按照LNilOrTagged处理
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
// 如果不支持Taggedpointer类型返回LReturnZero
b.eq LReturnZero
#endif
// p13 = isa [x0]存放的是class
ldr p13, [x0] // p13 = isa
// 从 GetClassFromIsa_p16 中获取clsss:p16 = class
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
// receiver->class 获取class,去class 中找method cache
LGetIsaDone:
// calls imp or 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
- 这里把
isa赋值给p13,并作为参数带入到GetClassFromIsa_p16中,源码中找到GetClassFromIsa_p16
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, \src // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src// 把当前的地址存入 p16(isa 存入 p16)
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
// $0, $1, #ISA_MASK $1就是 p13,就是 isa,与上ISA_MASK得到 class,并赋值给 p16
mov p16, \src
#endif
CacheLookup的实现
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
// 将x16的值,赋值给x15
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
// p1 = SEL, p16 = isa // => isa -> 类的 isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
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
// x16(isa) 平移 16 就得到了 cache的地址(即为_bucketsAndMaybeMask的地址)
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
// 如果_bucketsAndMaybeMask第 0 位不等于 0,就跳转到 LLookupPreopt\Function
tbnz p11, #0, LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7
// 通过哈希求 index 下标
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
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
- 通过对
objc_msgSend底层源码分析。我们知道了objc_msgSend的调用会根据不同的架构做不同的处理。 - 今天我们分析了
arm64真机环境下,objc_msgSend做了如下几件事:
- 通过
isa找到class; - 在
CacheLookup中通过isa平移找到cache即找到_bucketsAndMaybeMask的地址。 - 读取
buckets地址,即缓存的首地址。 - 获取
哈希的下标index。
整体流程:isa -> cache(_bucketsAndMaybeMask) -> buckets -> 哈希下标 index