1.Runtime运行时
1.1 概念
相比运行时,有一个编译时的概念
- 编译时:我们开发的时候,编程软件帮我们检查一下方法,语法是否存在错误,引用,环境是否有错误。会在编写代码的时候提醒你错误或者警告。
- 运行时:代码跑起来的时候,加载到内存的时候成为运行时。运行时发生错误,会导致程序发生崩溃。
1.2 运行时的调用方法的三种形式
- oc层,代码层,我们日常开发调用我们自定义的方法
[kbPerson test]
; - 系统的API调用,比如
[:isEqualto:];[isKindOf:]
;等 - 底层实现,比如
msgSend
,class_getInstanceSize
,等;
上图中:code
为日常编写代码,Compiler
为编译器层,会将代码翻译成某个中间状态的语⾔,同时会做一些LLVM编译器的优化,比如将alloc方法优化执行objc_alloc方法。
runtime system libarary
就是底层库。
2.方法的本质
我们在main 函数中
KBPerson *kbPerson = [KBPerson alloc];
[kbPerson sayNB];
通过clang编译后得到main.cpp文件,
KBPerson *kbPerson = ((KBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("KBPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)kbPerson, sel_registerName("sayNB"));
方法的本质是:((void (*)(id, SEL))(void *)objc_msgSend)()
一个objc_msgSend
的方法函数,id
是方法的接受者,谁调用这个方法,SEL
就是方法名
那么我们也可以仿照系统的方法进行编写先导入 #import <objc/message.h>,之后objc_msgSend设置进行不用检查
添加参数的时候:
[kbPerson sayNB:@"NB"];
objc_msgSend(kbPerson, @selector(sayNB:),@"NB");
/*((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)kbPerson, sel_registerName("sayNB:"), (NSString *)&__NSConstantStringImpl__var_folders_89_gg12jph17dg35tdtmwzd58180000gn_T_main_bb502a_mi_1);*/
添加对应的参数类型和参数。
我们在KBTeacher
中没有实现sayHello的方法,而是在他的父类中实现了,虽然有警告:Method definition for 'sayHello' not found
并不会崩溃,并且打印了该方法名
@interface KBPerson : NSObject
-(void)sayNB:(NSString*)name;
@end
@implementation KBPerson
-(void)sayNB:(NSString*)name{
NSLog(@"%s",__func__);
}
-(void)sayHello{
NSLog(@"%s",__func__);
}
@end
@interface KBTeacher:KBPerson
-(void)sayHello;
@end
@implementation KBTeacher
@end
@end
int main(int argc, const char * argv[]) {
KBPerson *kbPerson = [KBPerson alloc];
[kbPerson sayNB:@"NB"];
KBTeacher *kbTeacher = [KBTeacher alloc];
[kbTeacher sayHello];
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
虽然会说明子类会去查询父类的方法,如果有就会调用它,我们查看源码
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
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
给赋值后用objc_msgSendSuper
调用
KBTeacher *kbTeacher = [KBTeacher alloc];
struct objc_super kb_super;
kb_super.receiver = kbTeacher;
kb_super.super_class = KBPerson.class;
objc_msgSendSuper(&kb_super, @selector(sayHello));
super_class
是首先去调用查找的,找不到再去他的父类查找方法;
所以方法的本质是消息的发送,objc_msgSend
3. _objc_msgSend大致流程
我们通过汇编可知
进入objc_msgSend.
也验证了方法执行就是objc_msgSend的调用。我们进入源码可知
#endif
ENTRY _objc_msgSend //方法传入会有接受者和方法名
UNWIND _objc_msgSend, NoFrame
cmp p0, #0// 将接受者赋值给p0寄存器,同时判断接受者是否为0
//如果是0进行下面的判断
#if SUPPORT_TAGGED_POINTERS//是否支持target_pointers
b.le LNilOrTagged// 走下面的LNilOrTagged:方法
#else
b.eq LReturnZero //走下面的LReturnZero:方法,发送一个空的方法
#endif
ldr p13, [x0] // 把接受者的首地址也就是isa赋值给p13, p13 = isa
GetClassFromIsa_p16 p13, 1, x0 //通过这个方法拿到class 并赋值给 p16 = class,需要传入p13即 接收者的isa,1,以及接受者。
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached//获取到isa的话,进行缓存查找,
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check 检查对象是否为0,为0的话直接返回发送一个空方法
GetTaggedClass //获取小端对象的类
b LGetIsaDone//获取isa
// 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//结束
这里解释下,Tagged Pointer:
专门用来存储小的对象,例如NSNumber和NSDate。
Tagged Pointer指针的值不再是地址,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象"皮"的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free!在内存读取上有着以前3倍的效率,创建时比以前快106倍!
-
首先判断接受者
receiver
是否为空,为空的话,先判断是否支持小端模式,不支持的直接返回一个空,支持的话,在进入LNilOrTagged:
方法,判断对象是否为nil,是的话返回空方法,不是话获取类,以及类的isa。 -
把接受者的首地址存放在p13,也就是p13 = isa;通过GetClassFromIsa_p16方法传入isa,1,和接受者。把p16 = class的信息;
-
进入LGetIsaDone:方法,传入
NORMAL
,_objc_msgSend
,__objc_msgSend_uncached
进行CacheLookup
查找。
关于objc_msgSend
的具体详细流程接下来的篇章会进行探索