今天在學習Objective-C時見到某初始化函數中有如下代碼:
self = [super init];
if(self){
// Initialize members
}
return self;
當時只是覺得Objective-C與才C++應該一樣,每個子類對象都包含父類對象,所以super與self都是指向同一對象的頭部的,[super init]返回的地址應該與self所代表的地址一樣(即self == [super init])。那麼,對self賦[super init]的值豈不是沒有什麼意義?
找朱去討論,他也不知道是因爲什麼。但是他想起了在《From C++ to Objective-C》中好像提到過這個情況,於是在一通查找之後終於在7.1.3(P25)找到了說明。那一節的例子中的對應代碼是這樣的:
if(![super init])
return nil;
// Initialize members
return self;
同時下面又相應的說明:
Polemic: Most people use the instruction self = [super init]; if (self) {...} in the initializer. This is justified by the fact that in some cases, [super init] could return a different object. However, in a very interesting document [6], Will Shipley shows that is a bad practice. It is far more logical to use the above form, where it is only checked whether [super init] returns nil or not.
意思是很多人採用了self = [super init]; if (self) {...}的方法,這是由於有一些實踐已經證明了[super init]有可能返回一個(與self所指對象)完全不同的對象。但作者推薦了一篇文章,說這篇文章證實了這個爲大多數人所使用的初始化方式是一種不好的手法。
這裏所說的 a very interesting document[6] 指的是:
[6] Will Shipley. self = [supid init]. http://wilshipley.com/blog/2005/07/ self-stupid-init.html.
是一篇博文。在這篇博文裏,作者對比了兩種形式的初始化方法,它們是:
1.Traditional -init
- (id)init;
{
if ((self = [super init]) == nil)
return nil;
[...initialize my stuff...]
return self;
}
2.Wil's -init
- (id)init;
{
if (![super init])
return nil;
[...initialize my stuff...]
return self;
}
作者認爲不可能存在不用self = [super init]就不對的情況,所以他認爲第二種方法纔是正確的。他曾在以前的一篇博文上說誰能找到這樣的情況就給那人20美元。
但現在作者坦言這樣的情況確實存在——一個名叫Ken Ferry的人編寫了一個程序,該程序會自動爲Cocoa庫的每一個類派生出子類併產生程序將其實例化,並在初始化時判斷[super init]是否返回的就是self。最終該程序Ken Ferry終於找到了一些這樣的類——它們都是 single intance(單實例)的類。
說到這裏大家想必也都知道是怎麼回事了,單實例的類初始化函數在第一次被調用時會返回新的實例,以後再被調用則只會返回第一次生成的實例。在這種情況下,作者提出的方式2也就有了問題 ——若遇到父類採用單例模式,則在子類中[super init]返回的確實非nil,但卻也不再是子類對象所包含的那個父類對象了。如此一來子類對象所包含的那個父類對象並未被初始化,沒有達到初始化函數的目的。
但是我們也應該注意到第一種方式也是不對的——self被重新定向爲指向父類那個單實例,如此一來則發生了內存泄露,因爲self原先所指向的對象變成了“沒有鏈子栓着的狗”(沒有指針指向它)。
原來兩種方式都存在不足,那麼該如何是好呢?作者提出了第三種方式:
id superInitReturn = [super init];
if(!superInitReturn || self != superInitReturn)
{
return nil;
}
// Initialize memebers
return self;
我個人對此方法的理解是:若[super init]返回的與self不同,則說明父類是單實例類,而單實例類的init函數應該是被重寫過的,一般會返回nil,遇到這種情況我們就該收手了——讓你的類去繼承一個單實例類並非好的設計(如果想達到類似效果,請讓這個單實例成爲你的類的成員)——故而返回nil。
值得一提的是該博文爲作者2005年所作,作者於2009年在末尾追加了更新,指出蘋果公司有些說明表明了他們很有可能要重寫[NSObject init]方法,主要是因爲要採用新的內存管理手段以提高內存重用率,所以作者最終推薦的init方式爲:
-(id) init
{
if(!(self = [super init]))
{
return nil;
}
// Initialize members
return self;
}
原文如下:
Update April, 2009:
There's been hints from Apple that they might modify the standard -[NSObject init] method to try to re-use old object's memory, since it turns out that a very common usage pattern is for programs to keep creating and deallocating, say, 12 objects of the same class, over and over. Re-using the exact same memory ends up being a big win (and this is a trick the iPhone already does with its UITableViewCell class, and that is a HUGE win if you do it yourself on the iPhone).
So, from now on, I recommend everyone uses:
Subclassing NSColorPanel the Right Way
- (id)init;
{
if (!(self = [super init]))
return nil;
// other stuff
return self;
}
I do.
我還是感到不解,看不出他的理由是什麼。明天繼續研究,也希望有高手能來指點一二。