iOS开发学习笔记 self和super的区别
self和super
常见面试题引发的思考
一个很常见的面试题目如下:
代码一
@implementation Son: Father
- (instanacetype)init
{
self = [super init];
if (self)
{
NSLog(@"%@", [self class]);
NSLog(@"%@", [super class]);
NSLog(@"%@", [self superclass]);
NSLog(@"%@", [super superclass]);
}
return self;
}
@end
问:以上代码的输出是什么?
答案:输出为:
Son
Son
Father
Father
对应的问题也来了
- 子类初始化为什么要写
self = [super init]
,有什么意义? - 为什么
[self class]
和[super class]
、[super class]
和[super superclass]
输出相同?
self和super到底是什么呢?
大概解释
-
self
是一个对象指针,指向当前方法
的调用者/信息接收者
- 如果是实例方法,它就指向当前类的实例对象
- 如果是类方法,它就指向当前类的类对象
-
super
是一个编译器指令,当使用super
调用方法时,是告诉编译器从当前消息接收者的父类
中开始查找方法的实现
底层实现
[self class]
的调用底层实现
当使用self
调用方法时,OC在runtime会将其转换成消息发送出去,即换成objc_msgSend()
函数调用,并按照SEL
(此处为class)开始方法查找过程,找到了self
这个对象指针指向的对象就会调用该方法的实现IMP
,objc_msgSend()
函数大体声明如下:
id objc_msgSend(id theReceiver, SEL theSelector, ...)
结合代码一,这里的self
,其实就是Son类的一个实例对象,它的查找顺序为Son的方法列表->Father的方法列表->NSObject的方法列表
,最终class
方法在NSObject的方法列表
中找到,Son类的实例对象
就调用class
方法。
我们并未在OC源码中寻找NSObject
类中class
方法的实现,但根据输出可以知道实现大概如下:
- (Class) class
{
NSLog(@"%@", self.name);
}
最终输出:Son
[super class]
的调用底层实现
当使用super
调用方法时,OC编译器会生成一个objc_super
的结构体,他的组成大体如下:
struct objc_super {
id receiver; // 消息接收者
Class super_class; // 消息接收者的父类
}
生成该结构体之后,OC在runtime也会将该调用转换成消息发送出去,只是转换成的函数为objc_msgSendSuper()
,并按照SEL
从objc_super
的super_class
开始方法查找,找到之后,receiver
会调用该方法的实现IMP
。
结合以上代码,此时objc_super
结构体的receiver
就是Son的实例对象
,super_class
就是Son的实例对象中的super_class
即Father
,所以它的查找顺序为Father的方法列表->NSObject的方法列表
,最后receiver
也就是Son的实例对象
会调用NSObject类
的class
方法。
最终输出:Son
[self superclass]
和[self class]
的调用实现同理,[super superclass]
和[super class]
的调用实现同理。
回答问题
- 子类初始化为什么要写
self = [super init]
,有什么意义?
self = [super init]
是面向对象思想的一种体现,意义就是,利用父类的init方法为子类初始化父类的公有属性。
- 为什么
[self class]
和[super class]
、[super superclass]
和[super superclass]
输出相同?
因为他们实际上都是由
Son的实例对象
调用NSObject的class/superclass
方法得到的输出,所以是相同的。
深入探讨
代码二
#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()
@property(nonatomic, strong) Son *son;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_son = [[Son alloc] init];
Father *father = [[Father alloc] init];
[father go];
[self getMehtodsOfClass:[Son class]];
[self getMehtodsOfClass:[Father class]];
}
- (void)getMehtodsOfClass:(Class)cls{
unsigned int count;
Method* methods = class_copyMethodList(cls, &count);
NSMutableString* methodList = [[NSMutableString alloc]init];
for (int i=0; i < count; i++) {
Method method = methods[i];
NSString* methodName = NSStringFromSelector(method_getName(method));
[methodList appendString:[NSString stringWithFormat:@"| %@",methodName]];
}
NSLog(@"%@对象-所有方法:%@",cls,methodList);
free(methods);
}
@end
@implementation Father
- (void) go {
NSLog(@"%@ call Father func go, its class name is %@", self, [self class]);
[self eat];
NSLog(@"Father func go end");
}
- (void) eat {
NSLog(@"%@ call Father func eat, its class name is %@", self, [self class]);
}
@end
@implementation Son
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"In Son init");
NSLog(@"self class name is %@", [self class]);
NSLog(@"super class name is %@", [super class]);
NSLog(@"self superclass name is %@", [self superclass]);
NSLog(@"super superclass name is %@", [super superclass]);
[self go];
[super go];
NSLog(@"Son init end");
}
return self;
}
- (void) go {
NSLog(@"%@ call Son func go, its class name is %@", self, [self class]);
}
- (void) eat {
NSLog(@"%@ call Son func eat, its class name is %@", self, [self class]);
}
@end
输出:
In Son init
self class name is Son
super class name is Son
self superclass name is Father
super superclass name is Father
<Son: 0x600002610680> call Son func go, its class name is Son
<Son: 0x600002610680> call Father func go, its class name is Son
<Son: 0x600002610680> call Son func eat, its class name is Son
Father func go end
Son init end
<Father: 0x6000026002f0> call Father func go, its class name is Father
<Father: 0x6000026002f0> call Father func eat, its class name is Father
Father func go end
Son对象-所有方法:| init| go| eat
Father对象-所有方法:| go| eat
解析:
首先前五行很正常,之前已经解释过,重点在之后的两个调用:
[self go];
它的过程为:
-
首先是通过
self
调用,所以会转换成objc_msgSend()
函数,在Son类中寻找方法实现,最后该Son的实例对象son
直接调用Son类的go方法,输出<Son: 0x600002610680> call Son func go, its class name is Son
最终输出:
<Son: 0x600002610680> call Son func go, its class name is Son
然后是:
[super go];
它的过程为:
-
首先是通过
super
调用,所以会转换成objc_msgSendSuper()
函数,在Father类开始寻找方法实现,最后该Son的实例对象son
直接调用Father类的go方法,执行以下语句:- (void) go { NSLog(@"%@ call Father func go, its class name is %@", self, [self class]); [self eat]; NSLog(@"Father func go end"); } - (void) eat { NSLog(@"%@ call Father func eat, its class name is %@", self, [self class]); }
-
注意,在函数方法中,
self
总是指向函数的调用者/消息接收者。所以self
和[self class]
都是指向的Son的实例对象son
,所以先输出:<Son: 0x600002610680> call Father func go, its class name is Son
-
然后执行
[self eat]
,由于是通过self
调用方法,所以会转换成objc_msgSend()
函数调用,消息接收者是Son类的实例对象son
,所以在Son类中开始方法查找并在Son类中找到,所以由son
执行Son类的eat
方法,输出:<Son: 0x600002610680> call Son func eat, its class name is Son
-
最后执行
NSLog(@"Father func go end");
,输出:Father func go end
总结
- 如果通过
self
来调用方法,会转换成objc_msgSend()
方法,从函数调用者/消息接收者self
的类开始方法查找,最后由self
这个函数调用者/消息接收者调用该方法实现IMP
. - 如果通过
super
来调用方法,会预先构建objc_super
结构体,赋值其成员receiver
为函数调用者/消息接收者,该结构体中的super_class
为receiver的super_class指针
,并将函数调用转换成objc_msgSendSuper()
方法,从super_class
类中开始方法查找,最后由receiver
这个函数调用者/消息接收者调用该方法实现IMP
。