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
。