在Objective-C的开发中,经常使用self和super关键字,self是调用对象本身的方法,而super是调用父类的方法。那么本篇文章来研究一个有意思的问题。
一、提出问题
在objc源码工程中新建Person类并继承自NSObject,空实现;在新建Teacher类并继承自Person,然后添加一个-printClass方法:
// Teacher.h
@interface Teacher : Person
- (void)printClass;
@end
// Teacher.m
@implementation Teacher
- (void)printClass {
Class tCls = [self class];
Class sCls = [super class];
NSLog(@"class: %@, super: %@", tCls, sCls);
}
@end
将main.m中main函数修改如下:
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Teacher *teacher = [[Teacher alloc] init];
NSLog(@"teacher: %@", teacher);
[teacher printClass];
}
return 0;
}
运行程序,控制台输出如下:
2020-02-06 21:07:42.472001+0800 objc-debug[2013:81119] teacher: <Teacher: 0x100fc7450>
2020-02-06 21:07:42.472830+0800 objc-debug[2013:81119] class: Teacher, super: Teacher
Program ended with exit code: 0
可以看到,[self class]和[super class]输出的都是Teacher,可Teacher明明是继承自Person的,调用[super class]不是应该输出Person吗?
二、问题分析
2.1 源码分析
从NSObject的实例方法-class的实现分析一下:
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
它返回的是对象的isa,也就是类,按照这个实现,入参obj似乎只能是teacher实例。验证一下这个推测,将Teacher中-printClass方法修改一下,只研究[super class]:
// Teacher.m
@implementation Teacher
- (void)printClass {
Class sCls = [super class];
NSLog(@"super: %@", sCls);
}
@end
在第5行处打上断点,运行程序,来到上述断点后,再在object_getClass()函数的第一行打上断点,然后继续运行:

我们看到,入参
obj是Teacher类型实例,正是我们在main函数中创建的teacher(注意控制台输出是main函数的输出),也就是说,虽然是通过super关键字调用的-class方法,但毕竟-printClass是Teacher类的实例方法,在调用object_getClass()入参依然是teacher实例,那么teacher->isa获取到就是Teacher类
2.2 汇编分析
上面只是打了个断点从现象上分析了原因,但是原理是什么,并没有讲清楚。下面同汇编入手来探究一下其根本原因。在-printClass方法的第一行代码打上断点(上述代码块第5行),运行程序来到断点后,Xcode菜单->Debug->Debug Workflow->Always Show Disassembly,断点会来到汇编中:

调用
[super class]实际也是发送消息,调用了objc_msgSendSuper2(),在objc源码中搜索后发现是由汇编实现的,但在objc-abi.h中有定义:
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
其第一个参数是objc_super结构体对象,第二个参数是SEL,此处SEL也就是-class,看一下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.
__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};
objc_super有两个成员,一个receiver,即消息接受者,一个super_class,即父类。另外注意最后一行注释,说super_class是首先进行消息查找的类,也就是说,消息查找时,不查找当前类,直接从父类开始查找。
2.3 模拟向父类发送消息
通过前面的输出结果,以及上面的分析,可以推测objc_super的成员receiver就是teacher实例,根据这个推测我们模拟一下[super class]的过程。
将-printClass方法修改如下:
// Teacher.m
#import "Teacher.h"
#import "objc/message.h"
@implementation Teacher
- (void)printClass {
struct objc_super mySuper = {
self,
[Person class]
};
objc_msgSendSuper(&mySuper, @selector(class));
}
@end
可能会有下图所示的报错:

按照下图步骤修改即可:

然后
Person实现如下:
// Person.m
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (Class)class {
NSLog(@"person self: %@", self);
return object_getClass(self);
}
@end
运行程序,控制台输出(log实际会输出很多行,这是因为系统会自动多次调用-class方法):
...
2020-02-06 22:31:23.059597+0800 objc-debug[2625:124099] person self: <Teacher: 0x101127860>
2020-02-06 22:31:23.059694+0800 objc-debug[2625:124099] teacher: <Teacher: 0x101127860>
2020-02-06 22:31:23.059826+0800 objc-debug[2625:124099] person self: <Teacher: 0x101127860>
...
Person的实例方法中的self就是teacher实例,其实就是objc_super的成员receiver。为了验证这个说法,我们把receiver改一下:
- (void)printClass {
Teacher *tc = [[Teacher alloc] init];
NSLog(@"tc: %@", tc);
struct objc_super mySuper = {
tc,
[Person class]
};
objc_msgSendSuper(&mySuper, @selector(class));
}
新创建一个Teacher实例tc,将它作为receiver穿进去。运行程序:
2020-02-06 22:41:26.432620+0800 objc-debug[2700:130559] person self: <Teacher: 0x101931650>
2020-02-06 22:41:26.432882+0800 objc-debug[2700:130559] teacher: <Teacher: 0x101931650>
2020-02-06 22:41:26.440264+0800 objc-debug[2700:130559] person self: <Teacher: 0x101e01b50>
2020-02-06 22:41:26.440359+0800 objc-debug[2700:130559] tc: <Teacher: 0x101e01b50>
2020-02-06 22:41:26.440516+0800 objc-debug[2700:130559] person self: <Teacher: 0x101e01b50>
我们是在输出teacher之后才调用的-printClass,可以看到在此之后Person输出的self就是tc。
三、总结
在使用self和super关键字调用方法时,self和super并不是消息的接收者,而仅仅是告诉编译器,从什么地方开始查找方法,真正的消息接收者是当前对象本身。
比如在一个实例方法中执行[super xxx],实际会执行objc_msgSend(self, xxx, ...),第一个参数即消息接收者self,而不是super,因为super只是一个关键字,并不是一个可以作为消息接收者的对象。