前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[super class]和[self class]

[super class]和[self class]

作者头像
拉维
发布2019-11-21 11:34:38
6200
发布2019-11-21 11:34:38
举报
文章被收录于专栏:iOS小生活iOS小生活

引出问题

现在我们创建两个类:Parent和Son。

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface Parent : NSObject

@end

@implementation Parent

@end

@interface Son : Parent

@end

@implementation Son

- (instancetype)init{
    if (self = [super init]) {
        NSLog(@"self: %@",[self class]);
        NSLog(@"super: %@",[super class]);
    }
    return self;
}

@end

当在外界调用Son类的init后,输出如下:

代码语言:javascript
复制
2019-11-12 11:11:19.984281 testProject[434:111502] self: Son
2019-11-12 11:11:19.984387 testProject[434:111502] super: Son

按照我们的常规理解,super对应的应该打印Parent啊,为啥最后打印的是Son呢?

代码的真实调用情况

先来了解一下self和super的区别

1,self是当前方法的调用者,它是方法的隐藏参数。方法的隐藏参数还有一个,是_cmd参数,我们可以在调试的时候看到。

如果是在实例方法中,self代表的是当前类的实例对象;

如果是在类方法中,self代表的就是当前的类对象。

2,super是编译器指令。

接下来,我们将上述代码写在一个文件中,命名为Person.m文件,放在桌面的clang文件夹中。

打开终端,cd到Person.m目录(即clang文件夹)下,然后执行clang -rewrite-objc Person.m命令,这样在clang目录中可以看到多了一个Person.cpp文件。Person.cpp是clang命令编译Person.m文件的输出(相当于我们Xcode的编译操作)。

打开Person.cpp文件,我们看到有两段代码是需要我们关注的:

代码语言:javascript
复制
......省略的代码......
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) {} 
};
......省略的代码......

//这个地方就是我们Son类的init方法
static instancetype _I_Son_init(Son * self, SEL _cmd) {
    if (self = ((Son *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("init"))) {


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_12a896_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_12a896_mi_1,((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class")));
    }
    return self;
}
......省略的代码......

接下来我们来分析一下这段代码

我们只看[self class]和[super class]相关的代码:

代码语言:javascript
复制
// [self class] 等价于下面代码
((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))

// [super class] 等价于下面代码
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"))

// [self class]  和  [super class] 代码声明区别如下:
//[self class]
objc_msgSend(id, SEL)
//[super class]
objc_msgSendSuper(__rw_objc_super *, SEL)

解释一下区别:
1. [self class]代码底层是objc_msgSend(id, SEL),
   [super class]代码底层是objc_msgSendSuper(__rw_objc_super *, SEL)
2. SEL 是方法选择器 都是 - (Class)class 方法
3. 它们的方法名字不同 
4. 第一个参数不同,objc_msgSend的第一个参数是id, 
    objc_msgSendSuper的第一个参数是 __rw_objc_super 是一个结构体,如下:
struct __rw_objc_super { 
    struct objc_object *object; //当前对象,是self即Son类
    struct objc_object *superClass; //当前对象的父类对象,即Parent
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

// [self class]  和  [super class] 代码实现区别如下
//[self class]
objc_msgSend(
       (id)self, //当前类,即Son类
       sel_registerName("class")//从self类中开始查找class方法
)
//[super class]
objc_msgSendSuper(
        (__rw_objc_super){
                          (id)self, //还是这个self,这个和上面objc_msgSend中的self是一样的,都是Son这个类
                          (id)class_getSuperclass(objc_getClass("Son"))//从class_getSuperclass(objc_getClass("Son")类(即Parent类)中开始查找class方法
                         }, 
        sel_registerName("class")//查找的方法
)

从上述分析可以看出,[self class]和[super class]的区别就是objc_msgSend(id, SEL)和objc_msgSendSuper(__rw_objc_super *, SEL)的区别:

  1. objc_msgSend查找class方法的起始位置是当前类Son
  2. objc_msgSendSuper查找class方法的其实位置是class_getSuperclass(objc_getClass("Son")),即Parent类。最终,objc_msgSendSuper也会转为objc_msgSend((id)self, sel_registerName("class"))的方式

所以,二者只是查找class的起始位置不同,其最终调用方式是完全一致的,这是第一个关键点。

上面我们已经知道了,[self class]和[super class]的区别在于二者对class方法的查找起始位置不同,接下来我们就来聊聊他们的具体查找流程。

首先你要知道Son继承自ParentParent继承自NSObject

代码语言:javascript
复制
[self class]调用Son类中的class方法,找到class方法后调用即可;
如果没有找到class方法,就开始向父类即Parent类中查找;
如果还没有找到的话,开始向NSObject类中找。
当然,class方法在Son类中是没有实现的,所以此处调用的是NSObject类中的class方法
因此,class的查找顺序是Son -> Parent -> NSObject 中的class方法
代码语言:javascript
复制
[super class]调用Parent类中的class方法,找到class方法后调用即可;
如果没有找到class方法,就开始向父类即NSObject类中查找。
当然,class方法在Parent类也没有实现,所以此处调用的也是NSObject类中的class方法
因此,class的查找顺序是 Parent -> NSObject 中的class方法

那么现在问题来了,既然都是在NSObject中找到的class方法,那么为什么输出的都是Son呢?且听我慢慢分析。

在NSObject中,class方法的实现是这样的:

代码语言:javascript
复制
-(Class)class { 
  return object_getClass(self); //这个self是Son类的对象
}

[self class]和[super class]最终都是执行objc_msgSend((id)self, sel_registerName("class")),也就是说,他们最终都是给self发送一个名为class的消息。

也就是说,二者最终都会执行到这个object_getClass(self)函数,而这个函数中的参数self都是指的Son类的实例对象,因此,[self class]和[super class]都是指的Son类。

总结

1,[self method]和[super method]的不同点是开始查找method方法的起始位置不同;

2,它们最终都是执行objc_msgSend((id)self, sel_registerName("method")),只是本例中有一个特殊点,在NSObject类的class方法中需要一个参数self,这个self就是调用这个方法的对象。

代码语言:javascript
复制
-(Class)class { 
  return object_getClass(self); //这个self是Son类的对象
}

Demo

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface Parent : NSObject

- (void)run;

@end

@implementation Parent

- (void)run{
    NSLog(@"Parent: run,当前self是:%@", NSStringFromClass([self class]));
}

@end

@interface Son : Parent

- (void)run;

@end

@implementation Son

- (void)run{
    NSLog(@"Son: run,当前self是:%@", NSStringFromClass([self class]));
    
}

- (instancetype)init{
    if (self = [super init]) {
        [self run];
        [super run];
    }
    return self;
}

@end

最终输出结果:
2018-02-12 14:56:23.916168 testProject[451:136266] Son: run,当前self是:Son
2018-02-12 14:56:23.916250 testProject[451:136266] Parent: run,当前self是:Son

对上述代码执行clang命令,可以看到编译后的源码:

代码语言:javascript
复制
// Parent.m 类的run编译
static void _I_Parent_run(Parent * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_e1bd12_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
}

// Son.m 类的run编译
static void _I_Son_run(Son * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_fz_rpw560ws5gxgsnqc6l8c6mqh0000gn_T_Parent_e1bd12_mi_1, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));

}
// Son.m 类的init编译
static instancetype _I_Son_init(Son * self, SEL _cmd) {
    if (self = ((Son *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("run"));
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("run"));
    }
    return self;
}

下面我们来分析。

[self run]最终会找到Son中的run方法,执行下述代码:

代码语言:javascript
复制
NSLog(@"Son: run,当前self是:%@", NSStringFromClass([self class]));

这里的self指的是Son的实例对象,没有异议,所以说这里的[self class]就是Son,没有问题。

[super run]编译之后是这样的:

代码语言:javascript
复制
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("run"));

也就是说,它不是通过调用objc_msgSend给对象发送消息,而是调用了objc_msgSendSuper:

代码语言:javascript
复制
objc_msgSendSuper((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("run"));
代码语言:javascript
复制
    objc_msgSendSuper的第一个参数是 __rw_objc_super 是一个结构体,如下:
struct __rw_objc_super { 
    struct objc_object *object; //当前对象,是self即Son类
    struct objc_object *superClass; //当前对象的父类对象,即Parent
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

objc_msgSendSuper的作用就是定义查找run方法的起始位置在Son类的父类Parent类中,而其最终还是会调用到objc_msgSend((id)self, sel_registerName("run"));。这里的self就是objc_msgSendSuper的第一个参数__rw_objc_super结构体中的object,它表示的是当前方法的调用者。

也就是说,[super run]最终会执行objc_msgSend((id)self, sel_registerName("run"));,这里的self就是run方法的真正调用者,即run消息的真正接收者,也就是Son的实例对象自身。

所以在Parent类的run方法里面:

代码语言:javascript
复制
 NSLog(@"Parent: run,当前self是:%@", NSStringFromClass([self class]));

这个self就是Son的实例对象,因此这里的[self class]表示的就是Son

以上。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档