1. Runtime
1.1 Runtime
介绍
Runtime
称之为运行时,它与编译时的区别:
- 编译时:源代码翻译成机器能识别的代码的过程,主要是对语言进行最基本的检查报错,即词法分析、语法分析等,是一个静态的阶段
- 运行时:代码跑起来,被装载到内存中的过程,如果此时出错,则程序会崩溃,是一个动态阶段
1.2 Runtime
的两个版本
Legacy
版本:早期版本
◦ 对应Objective-C 1.0编程接⼝
◦ ⽤于32位
的Mac OS X
平台上
Modern
版本:现⾏版本
◦ 对应Objective-C 2.0
编程接⼝
◦ 用于iPhone
程序和Mac OS X v10.5
及以后系统中的64位
程序
1.3 使用Runtime
的三种方式
- 通过
Objective-C
代码,例如:[person sayNB]
- 通过
Foundation
框架的NSObject
类定义的方法,例如:isKindOfClass
- 通过
Runtime API
,例如:class_getInstanceSize
Compiler
:编译器Runtime System Libarary
:Runtime
的底层系统库
官方文档:Objective-C Runtime Programming Guide
2. 方法本质
2.1 方法底层的实现
在main
函数中,写入以下代码:
LGPerson *person = [LGPerson alloc];
[person sayNB];
[person say:@"NB"];
生成cpp
文件,底层代码如下:
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_d8842a_mi_3);
方法的本质:objc_msgSend
消息发送
objc_msgSend
的参数:
- 消息接收者
- 消息主体(
SEL
+ 参数)
2.2 objc_msgSend
在Build Setting
中,将Enable Strict Checking of obc_msgSend Calls
设置为NO
导入头文件:
#import <objc/message.h>
在main
函数中,写入以下代码:
@interface LGPerson : NSObject
- (void)sayNB;
@end
@implementation LGPerson
- (void)sayNB{
NSLog(@"666");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
}
return 0;
}
-------------------------
//输出结果:
666
- 和调用
OC
方法的执行结果相同 sel_registerName
为@selector()
的底层实现
2.2 objc_msgSendSuper
子类调用父类方法时,可使用objc_msgSendSuper
,向父类发送消息
在objc源码中,找到objc_msgSendSuper
的定义
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
- 传入
objc_super
类型的结构体指针
找到objc_super
结构体的定义
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};
receiver
:消息接收者super_class
:传入第一查找的类,如果找不到,继续向所属父类一层一层的查找
在项目中调用objc_msgSendSuper
定义LGTeacher
,继承于LGPerson
@interface LGTeacher : LGPerson
- (void)sayNB;
@end
@implementation LGTeacher
@end
- 定义
sayNB
方法,但不实现
在main
函数中,写入以下代码:
struct lg_objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGTeacher *teacher = [LGTeacher alloc];
struct lg_objc_super lgSuper;
lgSuper.receiver=teacher;
lgSuper.super_class=[LGPerson class];
objc_msgSendSuper(&lgSuper, @selector(sayNB));
}
return 0;
}
-------------------------
//输出结果:
666
- 按照
objc_super
相同结构,定义lg_objc_super
结构体 - 我们已经知道
LGTeacher
中没有sayNB
方法的实现,所以super_class
直接传入LGPerson
的类对象 - 如果
super_class
传入LGTeacher
的类对象,打印结果相同。区别在于需要向父类多查找一层
3. objc_msgSend
汇编代码
在objc4-818.2
源码中,不同系统架构的汇编指令都有差异,我们只针对最常用的arm64
架构下的汇编代码进行探索
3.1 objc_msgSend
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_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
ENTRY _objc_msgSend
:入口p0
寄存器,存储消息接收者cmp p0, #0
:消息接收者和#0
比较
◦ 汇编代码中,没有nil
,只有0
和1
◦ 这里的#0
比较,可以理解消息接收者为nil
SUPPORT_TAGGED_POINTERS
:真机arm64
架构,SUPPORT_TAGGED_POINTERS
定义为1
b.le LNilOrTagged
:小于等于0
,进入LNilOrTagged
流程b.eq LReturnZero
:等于0
,进入LReturnZero
流程- 否则,消息接收者存在,继续执行代码
◦ ldr p13, [x0]
:将x0
寄存器取地址,赋值p13
寄存器
◦ p13
:存储消息接收者的isa
- 进入
GetClassFromIsa_p16
流程,传入isa
、1
、消息接收者
3.2 LNilOrTagged
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
b.eq
:等于0
,进入LReturnZero流程- 否则,小于
0
,继续执行代码 - 进入
GetTaggedClass
流程 - 进入
LGetIsaDone
流程
3.3 LReturnZero
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
- 返回
nil
3.4 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
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
- 入参:
◦ src
:isa
◦ needs_auth
:1
◦ auth_address
:消息接收者
- 当前不符合
SUPPORT_INDEXED_ISA
条件,跳过 - 符合
__LP64__
条件,不满足needs_auth
等于0
的条件,进入else
分支 ExtractISA p16, \src, \auth_address
:进入ExtractISA
流程
◦ 传入p16
寄存器,isa
,消息接收者
◦ p16
寄存器的值,存储的是什么不重要,因为传入到ExtractISA
中会被赋值
3.5 ExtractISA
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
$0
:p16
寄存器$1
:isa
and $0, $1, #ISA_MASK
:将isa & ISA_MASK
的结果,存储到p16
寄存器
◦ p16
:存储类对象地址
3.6 LGetIsaDone
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
objc_msgSend
的后续流程
◦ 消息接收者存在,并且得到类对象,继续LGetIsaDone
流程
- 进入
CacheLookup
缓存查找流程,也就是所谓的sel-imp
快速查找流程 - 内部会调用
imp
或objc_msgSend_uncached
3. 流程图
为什么要获取类对象?
- 在快速查找流程中,查找的
cache
存储在类对象中,所以必须拿到类对象才能进行后面的流程
总结
Runtime
介绍:
Runtime
称之为运行时- 与编译时的区别:
◦ 编译时:源代码翻译成机器能识别的代码的过程,主要是对语言进行最基本的检查报错,即词法分析、语法分析等,是一个静态的阶段
◦ 运行时:代码跑起来,被装载到内存中的过程,如果此时出错,则程序会崩溃,是一个动态阶段
Runtime
的两个版本:
Legacy
版本:早期版本
◦ 对应Objective-C 1.0编程接⼝
◦ ⽤于32位
的Mac OS X
平台上
Modern
版本:现⾏版本
◦ 对应Objective-C 2.0
编程接⼝
◦ 用于iPhone
程序和Mac OS X v10.5
及以后系统中的64位
程序
使用Runtime
的三种方式:
- 通过
Objective-C
代码,例如:[person sayNB]
- 通过
Foundation
框架的NSObject
类定义的方法,例如:isKindOfClass
- 通过
Runtime API
,例如:class_getInstanceSize
方法本质:
- 方法的本质:
objc_msgSend
消息发送 objc_msgSend
的参数:
◦ 消息接收者
◦ 消息主体(SEL
+ 参数)
sel_registerName
为@selector()
的底层实现objc_msgSendSuper
:向父类发送消息
objc_msgSend
汇编代码:
ENTRY _objc_msgSend
:汇编代码的入口- 消息接收者
receiver
不存在,返回nil
- 否则,
receiver
存在:
◦ 获取isa
◦ isa & ISA_MASK
,得到类对象
◦ 成功得到类对象,进入CacheLookup
缓存查找流程,也就是所谓的sel-imp
快速查找流程