[self class]和[super class]

今天在学习runtime的时候,碰到一个有意思的题目,相信很多人都曾经看到过:

1
2
3
4
5
6
7
8
9
10
11
@implementation Son : Father

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

输出为Son和Son,为什么呢?当我们使用 clang -rewrite-objc Son.m文件,可以看到

1
2
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_bf216e_mi_1, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_bf216e_mi_2, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"))));

这里需要明白以下几个概念:

方法中的隐藏参数 self

我们经常在方法中使用self关键字来引用实例本身,但从没有想过为什么self就能取到调用当前方法的对象吧。其实self的内容是在方法运行时被偷偷的动态传入的。当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

  • 接收消息的对象(也就是self指向的内容)
  • 方法选择器(_cmd指向的内容)
    之所以说它们是隐藏的是因为在源代码方法的定义中并没有声明这两个参数。它们是在代码被编译时被插入实现中的。尽管这些参数没有被明确声明,在源代码中我们仍然可以引用它们。
1
2
static void _I_MyObject_test(MyObject * self, SEL _cmd) {
}

使用 clang 将代码转换成C++实现,我们可以看到方法的两个隐藏参数 self 和 _cmd

objc_msgSend 和 objc_msgSendSuper的定义

先给出 objc_msgSend 和 objc_msgSendSuper的定义:

1
2
3
id objc_msgSend(id self, SEL op, ...)
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

而 ‘objc_super’的定义如下:

1
2
3
4
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};

- (Class)class 方法的实现

1
2
3
- (Class)class {
return object_getClass(self);
}

现在再让我们看一下调用 [super class] 会发生什么

  • 当我们对 super 关键字发送消息时,编译器会创建一个 objc_super 的结构体,其中 receiver 仍旧为 self,而super_class为父类
  • 调用 objc_msgSendSuper()方法,会去 Father 的实例方法列表中寻找 class 这个方法,找不到,去 NSObject 中查找
  • 调用 NSObject 的 - (Class)class 方法,因为隐藏参数 self 为 son的实例,所以返回 Son

所以当我们调用 [self class] 和 [super class] 方法返回的都是 Son。

加入我们在Father中重载 -(Class)class 方法

1
2
3
- (Class)class {
return NSClassFromString(@"Father")
}

那么得到的输出将变成 Son 和 Father!

作者

千行

发布于

2019-06-13

更新于

2022-10-21

许可协议

评论