美文网首页
第八节—OC方法的本质

第八节—OC方法的本质

作者: L_Ares | 来源:发表于2020-10-24 01:37 被阅读0次

本文为L_Ares个人写作,以任何形式转载请表明原文出处。

本节开始就进入Runtime这套api的探索环节,那么延续cache_t带来的方法缓存,我们就从方法入手,进入Runtimeapi

在探究本质的这个工作上,我们之前探究过类的本质,那么探究方法的本质,也用同样的方法,通过Clang,查看我们的编译文件,然后去探索OC的方法会被编译成什么。

一、查看OC方法的编译文件

这次就不用objc781了,直接创建一个macos下的commond line tool。名字随意,创建一个iOS的类,继承于NSObject,名字随意,我取一个JDPerson

JDPerson里面随便设置一个实例方法,一个类方法,随意的给个实现。

然后在main.m里面加入<objc/runtime.h>#import "JDPerson.h"

代码如下 :

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "JDPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        JDPerson *person = [JDPerson alloc];
        [person study];
        [JDPerson teach];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}

操作 :

执行commod + B,没问题了以后,打开terminal终端,进入到当前项目下,在terminal终端输入如下指令 :

clang -rewrite-objc main.m -o main.cpp

结果 :

图1.png

打开main.cpp

别的都不用管,直接拉到最下面,main函数里面。

图2.png
        JDPerson *person = ((JDPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JDPerson"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("study"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JDPerson"), sel_registerName("teach"));

这里发现我们调用方法的[]全都变成了objc_msgSend

那我就可以有理由猜测,方法的本质就是通过objc_msgSend来发送消息。

二、验证

我们来验证一下方法的本质是不是通过objc_msgSend发送消息。

验证思路 :

现在我们不调用方法了,直接用objc_msgSend根据它的两个参数idSEL,传入这两个参数,看一下可不可以实现调用方法的结果。

如果实现了,那就证明objc_msgSend和调用方法是等价的。

而调用方法会被编译成objc_msgSend,所以objc_msgSend才是更接近汇编的,所以方法的本质可以说就是调用objc_msgSend发送消息。

那么直接调用objc_msgSend发送消息之前,需要做一个调整。

打开TARGETS--->Build Settings--->Apple Clang - Preprocessing
关闭objc_msgSend的严格检查。

图3.png

问 :

为什么要关闭?

答 :

因为objc_msgSend隶属于Runtime,本身就是动态的方法,中间会有很多的参数出现,如果想要静态的编译它,就像C语言一样的调用函数,那就取消这个检查机制。

操作 :
记得导入<objc/message.h>,否则无法使用objc_msgSend

下面上代码 :

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "JDPerson.h"
#import <objc/message.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        JDPerson *person = [JDPerson alloc];
        objc_msgSend(person,sel_registerName("study"));
        [person study];
        NSLog(@"Hello, World!");
    }
    return 0;
}

结果 :

图4.png

这个就证明了,objc_msgSend和调用实例方法是一样的效果吧。

下面看一下类方法可不可以。

再创建一个类JDStudent继承于JDPerson。添加一个JDStudent的实例方法doHomeWork,并随意实现一下。

然后看代码 :

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "JDPerson.h"
#import <objc/message.h>
#import "JDStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        JDPerson *person = [JDPerson alloc];
        objc_msgSend(person,sel_registerName("study"));
        [person study];
        
        objc_msgSend(objc_getClass("JDStudent"), sel_registerName("teach"));
        NSLog(@"Hello, World!");
    }
    return 0;
}

结果 :

图5.png

这证明类方法也是通过objc_msgSend发送消息才实现的方法吧。

结论 :

方法的本质就是调用objc_msgSend发送消息。

三、对象发送消息给父类

上面的两个实现,都是父类自己去根据SEL找到了方法实现的。

下面我们让子类对象直接发消息给父类,看看可不可以实现方法。

这里就要用到objc_super,下面看一下objc_super是什么。

#ifndef OBJC_SUPER
#define OBJC_SUPER

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    //指定类的一个实例
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
    //指定message实例的特定父类
    //如果没有定义__cplusplus并且没有定义__OBJC2__,这个肯定是不会走的,因为我们现在用的就是objc2.0的runtime
#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 */
};
#endif

objc_super这个结构体其实就是指定一个子类实例,和这个实例要把消息发送给哪个父类。就是说儿子想用父亲的东西,于是发消息给父亲。

  • receiver : 一个类的实例

  • super_class : receiver想要父类。

还有一个objc_msgSendSuper

objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
  • objc_msgSendSuper : 发送消息给一个实例的父类。

参数 :

  • objc_super : 结构体实例

  • SEL : 父类的方法名

下面上代码 :

        JDStudent *student = [JDStudent alloc];
        struct objc_super jdSuper;
        jdSuper.receiver = student;
        jdSuper.super_class = [JDPerson class];
        objc_msgSendSuper(&jdSuper, @selector(study));

结果 :

图6.png

那么子类向父类的类方法发送消息也可以试一下。

代码 :

        struct objc_super myJDSuper;
        myJDSuper.receiver = [student class];
        myJDSuper.super_class = class_getSuperclass(object_getClass([student class]));
        objc_msgSendSuper(&myJDSuper, sel_registerName("teach"));

结果 :

图7.png

也是可以的,毕竟类就是元类的实例对象,也就是实例类对象。

但是这里发现了一个问题,看一下下面的代码,依然可以正确的发送消息 :

        //实例发送消息给父类执行父类的实例方法
        JDStudent *student = [JDStudent alloc];
        struct objc_super jdSuper;
        jdSuper.receiver = student;
//        jdSuper.super_class = [JDPerson class];
        jdSuper.super_class = [JDStudent class];
        objc_msgSendSuper(&jdSuper, @selector(study));
        
        //子类发送消息给父类的类方法
        struct objc_super myJDSuper;
        myJDSuper.receiver = [student class];
//        myJDSuper.super_class = class_getSuperclass(object_getClass([student class]));
        myJDSuper.super_class = object_getClass([student class]);
        objc_msgSendSuper(&myJDSuper, sel_registerName("teach"));

执行结果是一模一样的。

图8.png

可是实例对象的父类已经变成了自己的类,为什么还能objc_msgSendSuper过去?

还有上面的类发消息给类方法,类那里明明用的是子类,但是发消息却是发给了父类,这样也可以发消息吗?

这个其实就是继承链的影响和更为主要的objc_msgSend的查找流程了。

下一节再说。本节就结束了,上面已经确认了结论,就不重复写了。

相关文章

  • 第八节—OC方法的本质

    本文为L_Ares个人写作,以任何形式转载请表明原文出处。 本节开始就进入Runtime这套api的探索环节,那么...

  • 认识Runtime运行时机制

    OC方法的本质 首先了解OC方法的本质到底是什么: OC方法由两个部分组成:SEL: 方法编号(一本书的目录编号)...

  • OC 方法的本质

    探索 探索案例 使用clang命令把oc代码编译成c代码分析 clang -rewrite-objc main.m...

  • OC底层方法的本质、查找流程

    1. 前言 前面的文章了解了OC对象的本质、类的本质以及方法缓存的原理,那么这篇文章将来分析一下OC方法底层的原理...

  • Runtime-消息三步处理机制

    Runtime 方法调用本质 OC是一门runtime语言,OC调用方法的实际,其实就是消息转发,我们可以通过底层...

  • OC 消息查找流程

    上一篇 OC 方法的本质 中提到OC的方法调用依赖于runtime实现的api(objc_msgSend、obj...

  • Runtime学习笔记

    一.消息机制 OC调用方法是动态调用 调用未实现的方法编译不报错 方法调用的本质是发送消息 方法调用的本质是 执行...

  • +load 和 +initialize 理解

    +load 底层本质。 OC runtime源码。 objc_init 方法里面 的 load_image 方法回...

  • iOS OC反汇编

    OC的反汇编 笔记暂时提交, 日后修正 1. OC方法本质 objc_msgSend(id , sel), 汇编打...

  • OC 与 Swift

    OC对象的本质(上):OC对象的底层实现原理OC对象的本质(中):OC对象的种类OC对象的本质(下):详解isa&...

网友评论

      本文标题:第八节—OC方法的本质

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