Runtime原理探究(五)—— super的本质


Runtime系列文章

Runtime原理探究(一)—— isa的深刻体会(苹果对isa的优化)面试

Runtime原理探究(二)—— Class结构的深刻分析缓存

Runtime原理探究(三)—— OC Class的方法缓存cache_tmarkdown

Runtime原理探究(四)—— 刨根问底消息机制iphone

Runtime原理探究(五)—— super的本质ide

Runtime原理探究(六)—— 面试题中的Runtime函数


从面试题出发

请看以下代码post

****************************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] = NSObjectui

实际打印结果

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]的打印结果不是咱们所预期的。怎么回事呢?spa

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里查找方法

所以对于开头的面试题,真正的运行逻辑以下 CLStudent-->[self class]

CLStudent-->[super class]

CLStudent-->[self superclass]

CLStudent-->[super superclass]

小结

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原理探究(一)—— isa的深刻体会(苹果对isa的优化)

Runtime原理探究(二)—— Class结构的深刻分析

Runtime原理探究(三)—— OC Class的方法缓存cache_t

Runtime原理探究(四)—— 刨根问底消息机制

Runtime原理探究(五)—— super的本质

Runtime原理探究(六)—— 面试题中的Runtime

相关文章
相关标签/搜索