7,對象的初始化以及實例變量的作用域
?
本系列講座有著很強的前后相關性,如果你是第一次閱讀本篇文章,為了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊 這里 。 ?
?
?
到目前為止,我們都使用的是下列方式創建對象
?
這種new的方式,實際上是一種簡化的方式。筆者在這里總結一下前面幾章里面曾經提到過關于創建對象的2個步驟:
?
第一步是為對象分配內存也就是我們所說的allocation,runtime會根據我們創建的類的信息來決定為對象分配多少內存。類的信息都保存 在Class里面,runtime讀取Class的信息,知道了各個實例變量的類型,大小,以及他們的在內存里面的位置偏移,就會很容易的計算出需要的內 存的大小。分配內存完成之后,實際上對象里面的isa也就被初始化了,isa指向這個類的Class。類里面的各個實例變量,包括他們的超類里面的實例變 量的值都設定為零。
?
需要注意的是,分配內存的時候,不需要給方法分配內存的,在程序模塊整體執行的時候方法部分就作為代碼段的內容被放到了內存當中。對象的內容被放到了數據段當中,編譯好的方法的匯編代碼被放到了代碼段當中。在Objective C里面,分配內存使用下列格式:
?
?
NSObject已經為我們提供了諸如計算內存空間大小以及初始化isa還有把各個實例變量清零,毫無疑問NSObject已經非常出色的完成了內存分配的工作,在一般情況下,我們不需要重寫alloc方法。
?
第 二步是要對內存進行初始化也就是我們所說的Initialization。 初始化指的是對實例變量的初始化。雖然在alloc方法里面已經把各個實例變量給清零了,但是在很多情況下,我們的實例變量不能是零(對于指針的實例變量 而言,就是空指針)的,這樣就需要我們對實例變量進行有意義的初始化。
?
按照Objective-C的約定,當初始化的時候不需要參數的話,就直接使用init方法來初始化:
?
?
init是一個定義在NSObject里面的一個方法,NSObject明顯無法預測到派生類的實例變量是什么,所以同學們在自己的類里面需要重載一下init方法,在init方法里面把實例變量進行初始化。
?
但 是,需要強調的是,由于某種原因我們的init也許失敗了,比如說我們需要讀取CNBLOGS.COM的某個RSS,用這個RSS來初始化我們的對象,但 是由于用戶的網絡連接失敗所以我們的init也許會失敗,在手機應用當中的一些極端的情況下比如說有同學寫一個讀取網頁內容的程序,在網頁內容非常大的時 候,那么alloc也有可能會失敗,為了可以方便的捕獲這些失敗,所以我們在程序當中需要把上面的過程寫在一起:
?
if ?(對象名)

else

加上了上面的if語句我們的初始化過程就是完美的,當然我們有的時候不需要這個if語句。當我們的alloc和init永遠不會失敗的時候。關于初始化的時候的錯誤捕獲,筆者將在后面的章節里面論述。
?
為了我們寫程序方便和簡潔,在創建一個從NSObject派生的類的對象的時候,蘋果公司把alloc和init簡化成為new,我們在程序代碼當中使用任何一種方式都是可以的,具體怎么寫是同學們的喜好和自由。
?
到這里,有同學會問,如果我們的init需要參數怎么辦?按照Objective-C的約定,我們需要使用initWith...。也就是帶參數的變量初始化,這個也是本章的主要內容。
?
本章在講述initWith的同時,也將會順便的給大家介紹一下實例變量的作用域。
?
?
?
7.1,本章程序的執行結果
?
在 本章里面,我們將要繼續使用我們在第4章已經構筑好的類Cattle和Bull。從一般的面向對象的角度上來說,是不鼓勵我們改寫已經生效的代碼的。但是 本章的目的是為了使同學們可以很好的理解主題,所以筆者在這里暫時違反一下規則改寫了一下Cattle類,在里面追加了initWith方法,筆者也在 Cattle類里面追加了一些實例變量為了闡述實例變量的作用域的問題。由于在Cattle類里面筆者追加了一些東西,所以在Bull類里面改寫了 saySomething這個函數,讓我們的Bull可以說更多的內容。我們的redBull是這樣說的:
?
圖7-1,本章程序的執行結果
?
本章程序代碼晴點擊 這里 下載。?
?
?
再次強調 在實際的編程過程中,尤其是寫大型程序多人合作的時候,除非發現BUG,否則不要改寫已經生效的代碼。這樣會產生一些意想不到的結果,從而使其他的弟兄們或者姐妹們對你充滿怨言。?7.2,實現步驟
?
第一步,按照我們在第2章所述的方法,新建一個項目,項目的名字叫做07-InitWithAndIvarScope。如果你是第一次看本篇文章,請到這里參看第二章的內容。
?
第二步,按照我們在第4章的4.2節的第二,三,四步所述的方法,把在第4章已經使用過的“Cattle.h”,“Cattle.m”,“Bull.h”還有“Bull.m”, 導入本章的項目里面。然后把第6章里面的“MyNSObject.h”也導入到項目當中。
?
第三步,打開“Cattle.h”,修改成為下面的代碼并且保存:
?
#import <Foundation/Foundation.h> @interface Cattle : NSObject { int legsCount; @private bool gender; //male = YES female = NO @protected int eyesCount; @public NSString *masterName; } - (void)saySomething; - (void)setLegsCount:(int) count; - (id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName; @end?
第4步,打開“Cattle.m”,修改成下面的代碼并且保存:
?
?
#import "Cattle.h" @implementation Cattle -(void) saySomething { NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount); } -(void) setLegsCount:(int) count { legsCount = count; } -(id)init { [super init]; return [self initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"somebody"]; } - (id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName { legsCount = theLegsCount; gender = theGender; eyesCount = theEyesCount; masterName = theMasterName; return self; } @end?
第五步,打開“Bull.m”, ,修改成下面的代碼并且保存:
?
#import "Bull.h" @implementation Bull -(void) saySomething { NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); NSLog(@"I have %d eyes, my master is %@.", eyesCount,masterName); //List below is illegal //NSLog(@"My gender is %@",gender ? @"male" : @"female"); } -(NSString*) getSkinColor { return skinColor; } - (void) setSkinColor:(NSString *) color { skinColor = color; } @end?
第六步,打開“07-InitWithAndIvarScope.m”,修改成下面的代碼并且保存:
?
#import <Foundation/Foundation.h> #import "Bull.h" #import "Cattle.h" #import "MyNSObject.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Bull *redBull = [[Bull alloc] initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"that cowboy"]; [redBull setSkinColor:@"red"]; [redBull saySomething]; //legal, but not good redBull->masterName = @"that cowgirl"; //legal, but bad //redBull->eyesCount = 3; //Trying to access a private ivar, VERY bad thing //MyClass bullClass = redBull->isa; bool *redBullGender = (bool *)(redBull) + 8; NSLog(@"My gender is %@",*redBullGender ? @"male" : @"female"); [pool drain]; return 0; }?
第 七步,選擇屏幕上方菜單里面的“Run”,然后選擇“Console”,打開了Console對話框之后,選擇對話框上部中央的“Build and Go”,如果不出什么意外的話,那么應該出現入圖7-1所示的結果。如果出現了什么意外導致錯誤的話,那么請仔細檢查一下你的代碼。如果經過仔細檢查發現 還是不能執行的話,可以到 這里 下載筆者為同學們準備的代碼。 如果筆者的代碼還是不能執行的話,請告知筆者。
?
?
7.3,實例變量的作用域(Scope)
對于Objective-C里面的類的實例變量而言,在編譯器的范圍里面,是有作用域的。和其他的語言一樣,Objective-C也支持public,private還有protected作用域限定。
?
如果一個實例變量沒有任何的作用域限定的話,那么缺省就是protected。
如果一個實例變量適用于public作用域限定,那么這個實例變量對于這個類的派生類,還有類外的訪問都是允許的。
如果一個實例變量適用于private作用域限定,那么僅僅在這個類里面才可以訪問這個變量。
如果一個實例變量適用于protected作用域限定,那么在這個類里面和這個類的派生類里面可以訪問這個變量,在類外的訪問是不推薦的。
?
我們來看看“Cattle.h”的代碼片斷:
int legsCount; @private bool gender; //male = YES female = NO @protected int eyesCount; @public NSString *masterName;?
第一行的legsCount 的前面沒有任何作用域限定,那么它就是protected的。
?
第二行是在說從第二行開始的實例變量的定義為private的,和其他的關鍵字一樣,Objective-C使用@來進行編譯導向。
?
第三行的gender的作用域限定是private的,所以它適用于private作用域限定。
?
第四行是在說從第四行開始的實例變量的定義為protected的,同時第二行的private的聲明作廢。
?
第五行的eyesCount的作用域限定是protected的,所以它適用于protected作用域限定。
?
第六行是再說從第六行開始的實例變量的定義為public的,同時第四行的protected的聲明作廢。
?
第七行的masterName的作用域限定是public的,所以它適用于public作用域限定。
?
我們再來看看在派生類當中,private,protected還有public的表現。Bull類繼承了Cattle類,筆者改寫了一下“Bull.m”用來說明作用域的問題,請參看下面的代碼:
?
-(void) saySomething { NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); NSLog(@"I have %d eyes, my master is %@.", eyesCount,masterName); //List below is illegal //NSLog(@"My gender is %@",gender ? @"male" : @"female"); }?
我們來看看第3還有第4行代碼,我們可以訪問legsCount,eyesCount還有masterName。
?
在第6行代碼當中,我們試圖訪問gender這個Cattle的私有(private)屬性,這行代碼產生了編譯錯誤,所以我們不得不注釋掉第6行代碼。
?
?
好的,我們再來看看類的外部private,protected還有public的表現。請同學們打開“07-InitWithAndIvarScope.m”,參考一下下面的代碼:
?
//legal, but not good redBull->masterName = @"that cowgirl"; //legal, but bad //redBull->eyesCount = 3; //Trying to access a private ivar, VERY bad thing //MyClass bullClass = redBull->isa; bool *redBullGender = (bool *)(redBull) + 8; NSLog(@"My gender is %@",*redBullGender ? @"male" : @"female");?
在 第二行里面,我們訪問了masterName,由于在Cattle里面masterName是public的,Bull繼承了Cattle,所以我們可以 直接訪問masterName。但是這不是一種好的習慣,因為這不符合面向對象的基本思想。實際上,如果沒有特殊的理由,我們不需要使用public的。
?
第四行,我們試圖在類的外邊訪問protected變量eyesCount,在這里筆者的Xcode只是輕輕的給了一個警告,編譯成功并且可以運行。同樣,這種在類的外邊訪問類的protected變量是一個很糟糕的做法。
?
我 們還記得在Bull的saySomething里面我們曾經試圖訪問過gender,但是編譯器無情的阻止了我們,因為gender是私有的。但是,這僅 僅是編譯器阻止了我們,當我們有足夠的理由需要在類的外邊訪問private實例變量的時候,我們還是可以通過一些強硬的方法合法的訪問私有變量的,我們 的方法就是使用指針偏移。
?
我們首先回憶一下第6章 的6.6節的內容,isa里面保存了對象里面的實例變量相對于對象首地址的偏移量,我們得到了這個偏移量之后就可以根據對象的地址來獲得我們所需要的實例 變量的地址。在正常情況下,我們需要通過訪問類本身和它的超類的ivars來獲得偏移量的,但是筆者在這里偷了一個懶,先使用第七行的代碼MyClass bullClass = redBull->isa;通過Debugger獲得gender的偏移量,數值為8。然后在第8行里面,筆者通過使用指針偏移取得了gender 的指針然后在第9行實現了輸出。
?
由此可見,在Objective-C里面, 所謂的private還有protected只是一個Objective-C強烈推薦的一個規則,我們需要按照這個規則來編寫代碼,但是如果我們違反了這個規則,編譯器沒有任何方法阻止我們。
?
筆者認為在類的外部直接訪問任何實例變量,不管這個實例變量是public,private還是protected都是一個糟糕的做法,這樣會明顯的破壞封裝的效果,盡管這樣對編譯器來說是合法的。
?
?
7.4,initWith...
?
NSObject為我們準備的不帶任何參數的init,我們的類里面沒有實例變量,或者實例變量可以都是零的時候,我們可以使用NSObject為我們準 備的缺省的init。當我們的實例變量不能為零,并且這些實例變量的初始值可以在類的初始化的時候就可以確定的話,我們可以重寫init,并且在里面為實 例變量初始化。
?
但是在很多時候, 我們無法預測類的初始化的時候的實例變量的初始值,同時NSObject明顯無法預測到我們需要什么樣的初始值,所以我們需要自己初始化類的實例變量。
?
請同學們打開“Cattle.m”,我們參考一下下面的代碼:
?
-(id)init { [super init]; return [self initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"somebody"]; } - (id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName { legsCount = theLegsCount; gender = theGender; eyesCount = theEyesCount; masterName = theMasterName; return self; }?
?
從 第3行到第7行,筆者重寫了一下init。在init里面,筆者給通過調用initWith,給類的各個實例變量加上了初始值。這樣寫是很必要的,因為將 來的某個時候,也許有人(或者是自己)很冒失的使用init來初始化對象,然后就嘗試使用這個對象,如果我們沒有重寫init,那么也許會出現一些意想不 到的事情。
?
從第9行到第19行,是我們自己定義的initWith,代碼比較簡單,筆者就不在這里贅述了。需要注意的一點是,筆者沒有在這里調用 [super?init]; 。原因是Cattle的超類就是NSObject,初始化的過程就是初始化實例變量的過程,runtime已經為我們初始化好了NSObject的唯一實例變量isa,也就是Cattle的類的信息,所以我們不需要調用 [super?init]; 。在某些時候,超類的變量需要初始化的時候,請同學們在子類的init或者initWith里面調用 [super?init]; 。
?
請同學們再次打開“07-InitWithAndIvarScope.m”,參考下面的代碼片斷:
?
Bull *redBull = [[Bull alloc] initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"that cowboy"]; [redBull setSkinColor:@"red"]; [redBull saySomething];?
從第1行到第4行就是調用的initWith來初始化我們的redBull。
?
?
7.5,本章總結
?
非常感謝大家對筆者的支持!
?
我 們在本章里面介紹了2個比較輕松的話題,一個是實例變量的作用域,這個概念筆者個人認為對有一點面向對象編程經驗的人來說,不是什么新鮮的概念了。但是需 要注意的是,Objective-C并沒有強制我們遵守它的規則,他仍舊為我們提供了違反規則的機會,這一點上根C++比較類似。只要支持指針,就無法避 免使用者違反規則。事務都是一分為二的,當我們得到了訪問任何變量的自由之后,我們必須為訪問這些變量承擔后果。
?
第二個話題就是initWith。和其他的面向對象的語言不同,Objective-C沒有構造函數,它通過init還有initWith來初始化變量, 我們應該根據具體情況進行具體的分析,從而編寫我們的init還有initWith方法。
?
?
來源: http://www.cnblogs.com/yaski/archive/2009/04/19/1439304.html
?
?
?
?
?
?
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
