引言
消息转发
的本质:向对象
发送消息,是一个查找方法
的过程。在前面我们研究过类
,编译成c++
本质是一个叫objc_class
的struct指针
,
15033832-8973df891eda5a82.png
objc_class
里面有一个重要的成员cache_t
,用于方法
的缓存。
查找方法的过程
在底层即是一个根据sel
找到imp
的流程。
这里在探究消息转发概念之前,不得不提到一个在Objective-C
里面一个很重要的概念:runtime
。
-
运行时
是相对于编译时
来说的 -
编译时
,主要是做一些词法语法分析,也就是编译时类型检查
,编译时刻代码是不会被加载到内存里
。 -
运行时
:可执行文件被装载到内存
中,代码跑起来了,会做运行时检查
,但是和编译时
的检查又不一样
,不是简单的扫描代码,而是在 内存中做些操作,做些判断。
举例如下:
创建两个类WJPerson
和WJTeacher
,并且WJTeacher 继承于WJPerson
。
WJTeacher:
#import <Foundation/Foundation.h>
#import "WJPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface WJTeacher : WJPerson
//声明并实现
- (void)playFootball;
//只声明不实现,在父类实现
- (void)playBasketball;
@end
NS_ASSUME_NONNULL_END
#import "WJTeacher.h"
@implementation WJTeacher
- (void)playFootball{
NSLog(@"%s",__func__);
}
@end
WJPerson:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WJPerson : NSObject
//这里方法没带参数,有兴趣的可以把带参数的试试
- (void)playBasketball;
@end
NS_ASSUME_NONNULL_END
#import "WJPerson.h"
@implementation WJPerson
- (void)playBasketball{
NSLog(@"%s",__func__);
}
@end
在main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
WJTeacher *p = [WJTeacher alloc];
[p playFootball];
[p playBasketball];
}
return 0;
}
2021-07-01 16:43:14.786334+0800 KCObjcBuild[1925:49713] -[WJTeacher playFootball]
2021-07-01 16:43:14.786870+0800 KCObjcBuild[1925:49713] -[WJPerson playBasketball]
分析如上代码
:在WJTeacher
我们只是声明
了playBasketball
方法,但是控制台
仍然显示该方法调用成功
。为什么会这样?
首先:我们还是clang
下这段代码,看看编译
后到底是什么样子。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
WJTeacher *p = ((WJTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("WJTeacher"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("playFootball"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("playBasketball"));
//带参数的-(void)playWithWhom:(NSString*)name;
Dog*dog = ((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Dog"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)dog, sel_registerName("playWithWhom:"), (NSString *)&__NSConstantStringImpl__var_folders_4c_6y4z_qs972qgqg7g06frb7540000gn_T_main_48f0a4_mi_0);
}
return 0;
}
从而引出了我们所要研究的主题:objc_msgSend
objc_msgSend
image.png
这里发现,
objc_msgSend(void /* id self, SEL op, ... */ )
方法虽然出来了,但是参数
为空,需要进行一步设置
image.png
image.png
结论:[p playFootball]
等价objc_msgSend(p,sel_registerName("playFootball"))
。