• 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
  • 相关阅读:
    jquery之滚楼
    jquery之仿京东菜单
    jquery之鼠标移动[沸腾京东]
    jquery之飘雪
    jquery之手风琴
    jquery 开始与结束方法 loading窗
    JS对象与数组
    Selenium+Python浏览器调用:Firefox
    Python脚本检查网页是否可以打开
    ubuntu安装pycharm教程
  • 原文地址:https://www.cnblogs.com/lwfing/p/16320493.html
Copyright © 2020-2023  润新知