底层原理-07-Runtime运行时和方法的本质

436 阅读4分钟

1.Runtime运行时

1.1 概念

相比运行时,有一个编译时的概念

  • 编译时:我们开发的时候,编程软件帮我们检查一下方法,语法是否存在错误,引用,环境是否有错误。会在编写代码的时候提醒你错误或者警告。
  • 运行时:代码跑起来的时候,加载到内存的时候成为运行时。运行时发生错误,会导致程序发生崩溃。

1.2 运行时的调用方法的三种形式

  1. oc层,代码层,我们日常开发调用我们自定义的方法[kbPerson test]
  2. 系统的API调用,比如[:isEqualto:];[isKindOf:];等
  3. 底层实现,比如msgSendclass_getInstanceSize,等;

截屏2021-06-27 下午3.58.25.png上图中: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设置进行不用检查

截屏2021-06-27 下午7.39.06.png

添加参数的时候:

[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大致流程

我们通过汇编可知

截屏2021-06-27 下午9.14.22.png 进入objc_msgSend.

截屏2021-06-27 下午9.14.47.png 也验证了方法执行就是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倍!

  1. 首先判断接受者receiver是否为空,为空的话,先判断是否支持小端模式,不支持的直接返回一个空,支持的话,在进入LNilOrTagged:方法,判断对象是否为nil,是的话返回空方法,不是话获取类,以及类的isa。

  2. 把接受者的首地址存放在p13,也就是p13 = isa;通过GetClassFromIsa_p16方法传入isa,1,和接受者。把p16 = class的信息;

  3. 进入LGetIsaDone:方法,传入NORMAL_objc_msgSend, __objc_msgSend_uncached进行CacheLookup查找。
    关于objc_msgSend的具体详细流程接下来的篇章会进行探索