方法的本质
编译时和运行时
编译时:顾名思义就是正在编译的时候 . 那啥叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码 .(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.)
那编译时就是简单的作一些翻译工作 ,比如检查老兄你有没有粗心写错啥关键字了啊.有啥词法分析,语法分析之类的过程. 就像个老师检查学生的作文中有没有错别字和病句一样 .如果发现啥错误编译器就告诉你.如果你用微软的VS的话,点下build.那就开始编译,如果下面有errors或者warning信息,那都是编译器检查出来的.所谓这时的错误就叫编译时错误,这个过程中做的啥类型检查也就叫编译时类型检查,或静态类型检查(所谓静态嘛就是没把真把代码放内存中运行起来, 而只是把代码当作文本来扫描下). 所以有时一些人说编译时还分配内存啥的肯定是错误的说法.
运行时:就是代码跑起来了.被装载到内存中去了.(你的代码保存在磁盘上没装入内存之前是个死家伙.只有跑到内 存中才变成活的).而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样.不是简单的扫描代码.而是在内存中做些操作,做些判断.
objc_msgSend
SJPerson *p = [SJPerson alloc];
[p sayHello];
[p doSomething];
现在看一下方法调用在底层怎么实现,clang -rewrite-objc main.m -o main.cpp
执行代码生成C++文件:
SJPerson *p = ((SJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("SJPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayHello"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("doSomething"));
我们看到方法调用在底层都会执行objc_msgSend
,底层都是通过发消息来实现方法的调用。里面都有两个参数,第一个参数:消息接收者,第二个参数:sel
。现在我们在doSomething
方法添加一个参数,再生成对应的C++文件:
SJPerson *p = [SJPerson alloc];
[p sayHello];
[p doSomething:@"eat"];
SJPerson *p = ((SJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("SJPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayHello"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("doSomething:"), (NSString *)&__NSConstantStringImpl__var_folders_v__ry97dfms2zg0q25cjyhj2t6h0000gn_T_main_a20784_mi_0);
这时看到,doSomething
调用方法名多了个冒号,参数多了一个字符串,sel
+参数构成了消息的主体。既然底层实现是调用objc_msgSend
,那我们能不能直接通过这个函数来调用方法呢,答案是可以的,但是需要改下build setting
里面的设置Enable Strict Checking of objc_msgSend Calls
,这个默认是YES
,默认一个参数,我们要改为NO
,就是我们非常牛逼,知道objc_msgSend
里面传什么参数,不用你管。
objc_msgSend(p, @selector(sayHello));
objc_msgSend(p, sel_registerName("doSomething:"), @"work");
这两行代码和上面代码是等价的。
新建一个子类SJStudent
继承SJPerson
,新建SJStudent
对象调用sayHello
:
SJStudent *s = [SJStudent alloc];
[s sayHello];
生成C++,里面会有objc_msgSendSuper
,子类没有sayHello
,肯定是从父类找,我们能不能通过objc_msgSendSuper
调用直接给父类发消息呢?首先看下这个方法的结构:
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
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_msgSendSuper
第一个参数是个结构体,结构体里面有两个参数receiver
和super_class
,第二个参数是sel
,以及方法的参数。
struct objc_super sj_objc_super;
sj_objc_super.receiver = s;
sj_objc_super.super_class = p.class;
objc_msgSendSuper(&sj_objc_super, @selector(sayHello));
上面代码与[s sayHello]
等价。有个问题,我super_class
能写别的参数吗,答案是可以,我们看注释super_class is the first class to search
,也就是说,这个class
是第一个方法查找的类。
总结:方法本质底层通过消息机制,利用objc_msgSend
给receiver
发送消息。参数有消息接收者和消息的主体,消息主体又包含Sel
和方法所有参数。