1. 程式人生 > 其它 >iOS開發學習筆記 self和super的區別

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