美文网首页
方法的本质

方法的本质

作者: 镜像 | 来源:发表于2021-06-30 18:51 被阅读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和方法所有参数。

相关文章

  • 01--方法本质03--面试题分析

    对方法的探索,全篇分六个章节01-方法本质-方法初探02-方法本质-objc_msgSend的使用03-方法本质-...

  • 01--方法本质04--lookUpImpOrForward 介

    对方法的探索,全篇分六个章节01-方法本质-方法初探02-方法本质-objc_msgSend的使用03-方法本质-...

  • 01--方法本质02--objc_msgSend的使用

    对方法的探索,全篇分六个章节01-方法本质-方法初探02-方法本质-objc_msgSend的使用03-方法本质-...

  • 01--方法本质06--消息转发流程

    对方法的探索,全篇分六个章节01-方法本质-方法初探02-方法本质-objc_msgSend的使用03-方法本质-...

  • 01--方法本质05--消息查找流程

    对方法的探索,全篇分六个章节01-方法本质-方法初探02-方法本质-objc_msgSend的使用03-方法本质-...

  • 方法的本质

    探索方法的本质 一个最基本的方法调用代码 方法的调用底层到底是个什么东西呢我们可以利用clang的一些命令 cla...

  • 方法的本质

    编译时和运行时 编译时:顾名思义就是正在编译的时候 . 那啥叫编译呢?就是编译器帮你把源代码翻译成机器能识别的代码...

  • 方法的本质

    1.消息的快速查找主要源码在汇编中,大概逻辑就是去cache_t里面查找2.消息的慢速查找核心 lookUpImp...

  • iOS底层原理总结 - 探寻Runtime本质(三)

    方法调用的本质 本文我们探寻方法调用的本质,首先通过一段代码,将方法调用代码转为c++代码查看方法调用的本质是什么...

  • Runtime的本质3-方法调用的本质

    1. 方法调用的本质 本文我们探寻方法调用的本质,首先通过一段代码,将方法调用代码转为c++代码查看方法调用的本质...

网友评论

      本文标题:方法的本质

      本文链接:https://www.haomeiwen.com/subject/keikultx.html