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

对应的问题也来了

  1. 子类初始化为什么要写self = [super init],有什么意义?
  2. 为什么[self class][super class][super class][super superclass]输出相同?

self和super到底是什么呢?

大概解释

  • self是一个对象指针,指向当前方法调用者/信息接收者

    • 如果是实例方法,它就指向当前类的实例对象
    • 如果是类方法,它就指向当前类的类对象
  • super是一个编译器指令,当使用super调用方法时,是告诉编译器从当前消息接收者的父类中开始查找方法的实现

底层实现

  1. [self class]的调用底层实现

当使用self调用方法时,OC在runtime会将其转换成消息发送出去,即换成objc_msgSend()函数调用,并按照SEL(此处为class)开始方法查找过程,找到了self这个对象指针指向的对象就会调用该方法的实现IMPobjc_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

  1. [super class]的调用底层实现

当使用super调用方法时,OC编译器会生成一个objc_super的结构体,他的组成大体如下:

struct objc_super {
    id receiver; // 消息接收者
    Class super_class; // 消息接收者的父类
}

生成该结构体之后,OC在runtime也会将该调用转换成消息发送出去,只是转换成的函数为objc_msgSendSuper(),并按照SELobjc_supersuper_class开始方法查找,找到之后,receiver会调用该方法的实现IMP

结合以上代码,此时objc_super结构体的receiver就是Son的实例对象super_class就是Son的实例对象中的super_classFather,所以它的查找顺序为Father的方法列表->NSObject的方法列表,最后receiver也就是Son的实例对象会调用NSObject类class方法。

最终输出:Son

  1. [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_classreceiver的super_class指针,并将函数调用转换成objc_msgSendSuper()方法,从super_class类中开始方法查找,最后由receiver这个函数调用者/消息接收者调用该方法实现IMP
posted @ 2022-05-28 13:35  沐锋丶  阅读(89)  评论(0编辑  收藏  举报