第八节—OC方法的本质

本文为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的查找流程了。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342