本文为L_Ares个人写作,以任何形式转载请表明原文出处。
本节开始就进入Runtime
这套api
的探索环节,那么延续cache_t
带来的方法缓存,我们就从方法入手,进入Runtime
的api
。
在探究本质的这个工作上,我们之前探究过类的本质,那么探究方法的本质,也用同样的方法,通过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
结果 :
打开main.cpp
别的都不用管,直接拉到最下面,main
函数里面。
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
根据它的两个参数id
和SEL
,传入这两个参数,看一下可不可以实现调用方法的结果。
如果实现了,那就证明objc_msgSend
和调用方法是等价的。
而调用方法会被编译成objc_msgSend
,所以objc_msgSend
才是更接近汇编的,所以方法的本质可以说就是调用objc_msgSend
发送消息。
那么直接调用objc_msgSend
发送消息之前,需要做一个调整。
打开
TARGETS
--->Build Settings
--->Apple Clang - Preprocessing
关闭objc_msgSend
的严格检查。
问 :
为什么要关闭?
答 :
因为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;
}
结果 :
这个就证明了,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;
}
结果 :
这证明类方法也是通过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));
结果 :
那么子类向父类的类方法发送消息也可以试一下。
代码 :
struct objc_super myJDSuper;
myJDSuper.receiver = [student class];
myJDSuper.super_class = class_getSuperclass(object_getClass([student class]));
objc_msgSendSuper(&myJDSuper, sel_registerName("teach"));
结果 :
也是可以的,毕竟类就是元类的实例对象,也就是实例类对象。
但是这里发现了一个问题,看一下下面的代码,依然可以正确的发送消息 :
//实例发送消息给父类执行父类的实例方法
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"));
执行结果是一模一样的。
可是实例对象的父类已经变成了自己的类,为什么还能objc_msgSendSuper
过去?
还有上面的类发消息给类方法,类那里明明用的是子类,但是发消息却是发给了父类,这样也可以发消息吗?
这个其实就是继承链的影响和更为主要的objc_msgSend
的查找流程了。
下一节再说。本节就结束了,上面已经确认了结论,就不重复写了。