第八节—OC方法的本质

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

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

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

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

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

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

然后在main.m里面加入#import "JDPerson.h"

代码如下 :

#import 
#import 
#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_msgSend

下面上代码 :

#import 
#import 
#import "JDPerson.h"
#import 

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 
#import 
#import "JDPerson.h"
#import 
#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方法的本质)