Runtime笔记(五)—— super的本质
Runtime系列文章
Runtime笔记(一)—— isa的深入体会(苹果对isa的优化)
Runtime笔记(二)—— Class结构的深入分析
Runtime笔记(三)—— OC Class的方法缓存cache_t
Runtime笔记(四)—— 刨根问底消息机制
Runtime笔记(五)—— super的本质
[Runtime笔记(六)—— Runtime的应用...待续]-()
[Runtime笔记(七)—— Runtime的API...待续]-()
Runtime笔记(八)—— 面试题中的Runtime
从面试题出发
请看如下代码
****************************CLPerson.h
@interface CLPerson : NSObject
@end
****************************CLStudent.h
@interface CLStudent : CLPerson
@end
****************************CLStudent.m
@implementation CLStudent
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"[self class] = %@",[self class]);
NSLog(@"[self superclass] = %@",[self superclass]);
NSLog(@"[super class] = %@",[super class]);
NSLog(@"[super superclass] = %@",[super superclass]);
}
return self;
}
@end
****************************main.m
int main(int argc, char * argv[]) {
@autoreleasepool {
[[CLStudent alloc] init];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
请问调用[[CLStudent alloc] init];
会有什么打印结果。
CLStudent
[self class]
—— 这个应该没有什么疑问,结果应该是[self class] = CLStudent
[self superclass]
—— 这个应该没有什么疑问,结果应该是[self superclass] = CLPerson
[super class]
—— 我们重写父类方法的时候,如果需要执行父类方法的逻辑,通常会加一句[super 方法名]
,那么super
不是指向父类的指针呢,如果是的话,这里的打印结果应该是[super class] = CLPerson
[super superclass]
—— 根据上面的推断,那么这里应该是打印CLPerson
的父类,也就是[super superclass] = NSObject
实际打印结果
2019-08-12 20:59:23.580048+0800 iOS-Runtime[25274:2862267] [self class] = CLStudent 2019-08-12 20:59:23.580700+0800 iOS-Runtime[25274:2862267] [self superclass] = CLPerson 2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super class] = CLStudent 2019-08-12 20:59:23.580882+0800 iOS-Runtime[25274:2862267] [super superclass] = CLPerson
通过实际的代码调试,我们看到[super class]
和[super superclass]
的打印结果不是我们所预期的。怎么回事呢?
super是什么
要解释上面的问题,我们还是要回到问题的本质去探讨。首先我们简化一下代码
****************************CLPerson.h
@implementation CLStudent
- (void)run;
@end
****************************CLPerson.m
@implementation CLStudent
- (void)run {
NSLog(@"CLPerson Run");
}
@end
****************************CLStudent.m
@implementation CLStudent
- (void)run {
[super run];
NSLog(@"CLStudent Run");
}
@end
我们在命令行窗口通过命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CLStudent.m -o CLStudent.cpp
拿到CLStudent.m
编译后的中间代码CLStudent.cpp
在其中可以看到run
方法的底层实现如下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {
//[super run];
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("CLStudent"))}, sel_registerName("run"));
//NSLog(@"CLStudent Run");
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_CLStudent_5be081_mi_0);
}
*************🔧🔧🔧简化一下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {
//[super run];
objc_msgSendSuper((__rw_objc_super){
(id)self,
(id)class_getSuperclass(objc_getClass("CLStudent"))
},
@selector(run));
//NSLog(@"CLStudent Run");不重要,不用管
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_CLStudent_5be081_mi_0);
}
*************♥️♥️♥️再精简一下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {
//⚠️⚠️⚠️结构体单独抽出来
struct __rw_objc_super arg = {
(id)self,
(id)class_getSuperclass(objc_getClass("CLStudent"))
};
//🌞🌞🌞[super run];
objc_msgSendSuper(arg, @selector(run));
}
从精简后的代码,可以看出来,[super run];
底层实际上是调用了函数objc_msgSendSuper(arg, @selector(run));
,首先我们来分析一下它的两个参数。第二个参数相信不用多解释了,就是一个方法选择器SEL
,重点看一下第一个参数,这是一个结构体__rw_objc_super
,我们在当前的中间代码里面就可以找到其定义,或者,你也可以通过objc_super
在objc源码里面找到它的定义,如下所示
//♥️♥️♥️C++中间代码里的定义
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
//⚠️⚠️⚠️objc源码中的定义
/// 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 */
};
该结构题里面其实就两个成员变量
-
id receiver;
—— 消息接受者,其实实参传递的就是self
,也就是CLStudent
的实例对象 -
Class super_class;
—— 父类,通过中间码里面该结构体初始化的赋值代码(id)class_getSuperclass(objc_getClass("CLStudent")
可以看出,这个父类就是CLStudent
的父类类对象[CLPerson class]
.
接着我们在看一下objc_msgSendSuper
里面究竟干了什么事情。根据函数名字,我们可以在message.h
里面找到相关的申明
/**
* Sends a message with a simple return value to the superclass of an instance of a class.
*
* @param super A pointer to an \c objc_super data structure. Pass values identifying the
* context the message was sent to, including the instance of the class that is to receive the
* message and the superclass at which to start searching for the method implementation.
* @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method identified by \e op.
*
* @see objc_msgSend
*/
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
解读一下注释里面对于参数的说明
-
super
—— 是一个指向结构体指针struct objc_super *
,它里面的内容是{消息接受者 recv
,消息接受者的父类类对象 [[recv superclass] class]
},objc_msgSendSuper
会将消息接受者的父类类对象
作为消息查找的起点。 -
op
—— 消息接受者接受的消息/方法,也就是需要去查找的方法
为了加深理解,我们对比一下给对象正常发送消息后的查找流程
[obj message]
->在obj的类对象cls查找方法
->在cls的父类对象[cls superclass]查找方法
->在更上层的父类对象查找方法
-> ... ->在根类类对象 NSObject里查找方法
[super message]
->->在obj的类对象cls查找方法(跳过此步骤)
(直接从这一步开始)在cls的父类对象[cls superclass]查找方法
->在更上层的父类对象查找方法
-> ... ->在根类类对象 NSObject里查找方法
因此对于开头的面试题,真正的运行逻辑如下
小结
NSLog(@"[self class] = %@",[self class]);
消息接受者:
CLStudent
的实例对象最终调用的方法:基类
NSObject
的-(Class)class
方法2019-08-12 20:59:23.580048+0800 iOS-Runtime[25274:2862267] [self class] = CLStudent
NSLog(@"[super class] = %@",[super class]);
消息接受者:仍然是
CLStudent
的实例对象最终调用的方法:基类
NSObject
的-(Class)class
方法2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super class] = CLStudent
NSLog(@"[self superclass] = %@",[self superclass]);
消息接受者:
CLStudent
的实例对象最终调用的方法:基类
NSObject
的-(Class)superclass
方法2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [self superclass] = CLPerson
NSLog(@"[super superclass] = %@",[super superclass]);
消息接受者:仍然是
CLStudent
的实例对象最终调用的方法:基类
NSObject
的-(Class)superclass
方法2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super superclass] = CLPerson
因为-class
和-superclass
方法底层实际上里面是使用了Runtime的API,简单来说就是如下
- (Class)class
{
return object_getClass(self);
}
- (Class)superclass
{
return class_getSuperclass(object_getClass(self));
}
所以只要明白谁是真正的消息接受者,相信最后的打印结果就很容易理解了。
OK,super的本质探讨就到这里。
Runtime系列文章
Runtime笔记(一)—— isa的深入体会(苹果对isa的优化)
Runtime笔记(二)—— Class结构的深入分析
Runtime笔记(三)—— OC Class的方法缓存cache_t
Runtime笔记(四)—— 刨根问底消息机制
Runtime笔记(五)—— super的本质
[Runtime笔记(六)—— Runtime的应用...待续]-()
[Runtime笔记(七)—— Runtime的API...待续]-()
Runtime笔记(八)—— 面试题中的Runtime