一、Runtime运行时理解
Objective-C Runtime Describes the macOS Objective-C runtime library support functions and data structures.
OC运行时描述macOS Objective-C运行库支持的函数和数据结构。
先看代码
#import <Foundation/Foundation.h>
@interface ApplePerson : NSObject
-(void)sayNB;
-(void)say666;
@end
@implementation ApplePerson
-(void)sayNB{
NSLog(@"真牛逼");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ApplePerson *person = [[ApplePerson alloc] init];
[person sayNB];
[person say666];
}
return 0;
}
编译时候通过,但是跑的时候没有实现就直接报错,说明编译只检查语法
clang一下,看下编译后的代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
ApplePerson *person = ((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ApplePerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say666"));
}
return 0;
}
上面就是编译成C++的代码,去掉强转,可以看
sayNB在转成底层代码时候变成
objc_msgSend)(person, sel_registerName("sayNB"));
可以看出
sayNB是一个方法,在底层变成一个objc_msgSend(参数1,参数2)的消息发送,可以看出参数1是消息的接收者,参数2是方法名字
验证一下,把sayNB方法名和实现改一下
-(void)sayNB:(NSString*)nbStr;
-(void)sayNB:(NSString*)nbStr{
NSLog(@"%@",nbStr);
}
重新clang一下
ApplePerson *person = ((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)((ApplePerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ApplePerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("sayNB:"), (NSString *)&__NSConstantStringImpl__var_folders_h5_y3bndywn3fx_v059tvs_zzwh0000gn_T_main_9fefca_mi_1);
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say666"));
去掉强转,可以看到
(objc_msgSend)(person, sel_registerName("sayNB:"), (NSString *)&__NSConstantStringImpl__var_folders_h5_y3bndywn3fx_v059tvs_zzwh0000gn_T_main_9fefca_mi_1);
添加参数后,底层源码已经变了,
sayNB有分号:,并且后面多了一串NSString类型的参数值
所以推出消息发送是msg_send(消息接收者,消息主体(SEL + 参数));
修改一下say666方法
-(void)say666{
NSLog(@"666");
}
用底层代码调用
((void (*)(id, SEL))(void *)objc_msgSend)(person, sel_registerName("say666"));
运行打印
2021-11-19 16:51:13.911983+0800 RuntimeTest[4436:353227] 666
和用代码
[person say666]完全一样,再看下msgSend方法
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
传一个消息接收者,和方法名,改一下
objc_msgSend测试
((void (*)(id, SEL))(void *)objc_msgSend)([[ApplePerson alloc] init], @selector(say666));
运行
2021-11-19 16:58:42.168515+0800 RuntimeTest[4476:358119] 666
结果一样,可知方法的本质就是消息发送
二、自定义底层调用方法
在查看C++代码中和objc_msgSend发现了objc_msgSendSuper
#define __OBJC_RW_DLLIMPORT extern
__OBJC_RW_DLLIMPORT void objc_msgSend(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper(void);
应该是对父类发消息,定义一个类
AppleEmployee
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface ApplePerson : NSObject
-(void)sayNB;
-(void)say666;
@end
@implementation ApplePerson
-(void)sayNB{
NSLog(@"%s",__func__);
}
-(void)say666{
NSLog(@"666");
}
@end
@interface AppleEmployee : ApplePerson
-(void)sayNB;
@end
@implementation AppleEmployee
@end
AppleEmployee有sayNB方法,对其父类发送消息,查看objc_msgSendSuper源码
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
需要传入
struct objc_super,查看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.
#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 */
};
代码里自定义一个objc_super结构体,可以看出
class不需要,只需要receiver和super_class,代码如下
struct objc_super apple_objc_super;
apple_objc_super.receiver = person;
apple_objc_super.super_class = [ApplePerson class];
写好方法
((void (*)(id, SEL))(void *)objc_msgSendSuper)((__bridge id)(&apple_objc_super), sel_registerName("sayNB"));
运行
2021-11-23 17:39:43.054548+0800 RuntimeTest[12857:1375165] -[ApplePerson sayNB]