閱讀815 返回首頁    go 阿裏雲 go 技術社區[雲棲]


objc’s self and super

在objc中的類實現中經常看到這兩個關鍵字”self”和”super”,以以前oop語言的經驗,拿c++為例,self相當於this,super相當於調用父類的方法,這麼看起來是很容易理解的。

以下麵的代碼為例:

@interface Person:NSObject {
    NSString*  name;
}
- (void) setName:(NSString*) yourName;
@end

@interface PersonMe:Person {
    NSUInteger age;
}
- (void) setAge:(NSUInteger) age;
- (void) setName:(NSString*) yourName andAge:(NSUInteger) age;
@end

@implementation PersonMe
- (void) setName:(NSString*) yourName andAge:(NSUInteger) age {
    [self setAge:age];
    [super setName:yourName];
}
@end

int main(int argc, char* argv[]) {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]
    PersonMe* me = [[PersonMe alloc] init];
    [me setName:@"asdf" andAge:18];
    [me release];
    [pool drain];
    return 0;
}

上麵有簡單的兩個類,在子類PersonMe中調用了自己類中的setAge和父類中的setName,這些代碼看起來很好理解,沒什麼問題。
然後我在setName:andAge的方法中加入兩行:

NSLog(@"self ' class is %@", [self class]);
NSLog(@"super' class is %@", [super class]);

這樣在調用時,會打出來這兩個的class,先猜下吧,會打印出什麼?
按照以前oop語言的經驗,這裏應該會輸出:

self ' s class is PersonMe
super ' s class is Person

但是編譯運行後,可以發現結果是:

self 's class is PersonMe
super ' s class is PersonMe

self的class和預想的一樣,怎麼super的class也是PersonMe?

真相

self 是類的隱藏的參數,指向當前當前調用方法的類,另一個隱藏參數是_cmd,代表當前類方法的selector。這裏隻關注這個self。super是個啥?super並不是隱藏的參數,它隻是一個“編譯器指示符”,它和self指向的是相同的消息接收者,拿上麵的代碼為例,不論是用[self setName]還是[super setName],接收“setName”這個消息的接收者都是PersonMe* me這個對象。不同的是,super告訴編譯器,當調用setName的方法時,要去調用父類的方法,而不是本類裏的。

當使用self調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;而當使用super時,則從父類的方法列表中開始找。然後調用父類的這個方法。

One more step

這種機製到底底層是如何實現的?其實當調用類方法的時候,編譯器會將方法調用轉成一個C函數方法調用,apple的objcRuntimeRef上說:

Sending Messages

When it encounters a method invocation, the compiler might generate a call to any of several functions to perform the actual message dispatch, depending on the receiver, the return value, and the arguments. You can use these functions to dynamically invoke methods from your own plain C code, or to use argument forms not permitted by NSObject’s perform… methods. These functions are declared in /usr/include/objc/objc-runtime.h.
■ objc_msgSend sends a message with a simple return value to an instance of a class.
■ objc_msgSend_stret sends a message with a data-structure return value to an instance of
a class.
■ objc_msgSendSuper sends a message with a simple return value to the superclass of an instance of a class.
■ objc_msgSendSuper_stret sends a message with a data-structure return value to the superclass of an instance of a class.

可以看到會轉成調用上麵4個方法中的一個,由於_stret係列的和沒有_stret的那兩個類似,先隻關注objc_msgSend和objc_msgSendSuper兩個方法。

當使用[self setName]調用時,會使用objc_msgSend的函數,先看下objc_msgSend的函數定義:

id objc_msgSend(id theReceiver, SEL theSelector, ...)


第一個參數是消息接收者,第二個參數是調用的具體類方法的selector,後麵是selector方法的可變參數。我們先不管這個可變參數,以[self setName:]為例,編譯器會替換成調用objc_msgSend的函數調用,其中theReceiver是self,theSelector是@selector(setName:),這個selector是從當前self的class的方法列表開始找的setName,當找到後把對應的selector傳遞過去。

而當使用[super setName]調用時,會使用objc_msgSendSuper函數,看下objc_msgSendSuper的函數定義:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)


第一個參數是個objc_super的結構體,第二個參數還是類似上麵的類方法的selector,先看下objc_super這個結構體是什麼東西:

struct objc_super {
    id receiver;
    Class superClass;
};

可以看到這個結構體包含了兩個成員,一個是receiver,這個類似上麵objc_msgSend的第一個參數receiver,第二個成員是記錄寫super這個類的父類是什麼,拿上麵的代碼為例,當編譯器遇到PersonMe裏setName:andAge方法裏的[super setName:]時,開始做這幾個事:

1,構建objc_super的結構體,此時這個結構體的第一個成員變量receiver就是PersonMe* me,和self相同。而第二個成員變量superClass就是指類Person,因為PersonMe的超類就是這個Person。

2,調用objc_msgSendSuper的方法,將這個結構體和setName的sel傳遞過去。函數裏麵在做的事情類似這樣:從objc_super結構體指向的superClass的方法列表開始找setName的selector,找到後再以objc_super->receiver去調用這個selector,可能也會使用objc_msgSend這個函數,不過此時的第一個參數theReceiver就是objc_super->receiver,第二個參數是從objc_super->superClass中找到的selector

裏麵的調用機製大體就是這樣了,以上麵的分析,回過頭來看開始的代碼,當輸出[self class]和[super class]時,是個怎樣的過程。
當使用[self class]時,這時的self是PersonMe,在使用objc_msgSend時,第一個參數是receiver也就是self,也是PersonMe* me這個實例。第二個參數,要先找到class這個方法的selector,先從PersonMe這個類開始找,沒有,然後到PersonMe的父類 Person中去找,也沒有,再去Person的父類NSObject去找,一層一層向上找之後,在NSObject的類中發現這個class方法,而NSObject的這個class方法,就是返回receiver的類別,所以這裏輸出PersonMe。

當使用[super class]時,這時要轉換成objc_msgSendSuper的方法。先構造objc_super的結構體吧,第一個成員變量就是self, 第二個成員變量是Person,然後要找class這個selector,先去superClass也就是Person中去找,沒有,然後去Person的父類中去找,結果還是在NSObject中找到了。然後內部使用函數objc_msgSend(objc_super->receiver, @selector(class)) 去調用,此時已經和我們用[self class]調用時相同了,此時的receiver還是PersonMe* me,所以這裏返回的也是PersonMe。

Furthor more

在類的方法列表尋找一個方法時,還牽涉到一個概念類對象的isa指針和objc的meta-class概念,這裏就不再詳細介紹。

- [EOF] -

最後更新:2017-04-04 07:03:16

  上一篇:go cf 158div2 D. Black and White Tree
  下一篇:go 鮑爾默自賣自誇:Win 95也比不上Win 8