方法的本质

2021-06-30  本文已影响0人  镜像

编译时和运行时

编译时:顾名思义就是正在编译的时候 . 那啥叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码 .(当然只是一般意义上这么说,实际上可能只是翻译成某个中间状态的语言.)

那编译时就是简单的作一些翻译工作 ,比如检查老兄你有没有粗心写错啥关键字了啊.有啥词法分析,语法分析之类的过程. 就像个老师检查学生的作文中有没有错别字和病句一样 .如果发现啥错误编译器就告诉你.如果你用微软的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第一个参数是个结构体,结构体里面有两个参数receiversuper_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_msgSendreceiver发送消息。参数有消息接收者和消息的主体,消息主体又包含Sel和方法所有参数。

上一篇下一篇

猜你喜欢

热点阅读