原文連接地址:http://www.raywenderlich.com/2712/using-properties-in-objective-c-tutorialhtml
著做權聲明:本文由http://www.cnblogs.com/andyque翻譯,歡迎轉載分享。請尊重做者勞動,轉載時保留該聲明和做者博客連接,謝謝!java
教程截圖:程序員
這是在iphone上面使用objc,與內存管理有關的第三篇教程。objective-c
在第一篇教程中,咱們介紹了在objective-c裏面若是使用實例變量和引用計數來管理內存。編程
在第二篇教程中,咱們介紹瞭如何檢測內存泄露、與內存有關的易犯的錯誤,使用是Instruments以及其它輔助工具。數組
在這第三篇教程中,也是本系列的最後一篇教程,咱們將談一談objc的property。咱們將介紹property是什麼,它是怎樣工做的,有一些什麼樣的規則,以及使用它們能夠用來避免大部分與內存相關的問題。安全
若是你尚未本系列教程的樣例工程的話,能夠點擊這裏下載,咱們將從這個工程開始。多線程
讓咱們先回顧一下,本項目須要管理內存的地方在哪。app
目前RootViewController有兩個實例變量:_sushiTypes, 和 _lastSushiSelected.eclipse
@interface RootViewController : UITableViewController { NSArray * _sushiTypes; NSString * _lastSushiSelected; } @end
對於_sushiTypes,咱們是在viewDidLoad裏面經過alloc/init的方式來建立的,而後在viewDidUnload和dealloc裏面release。
// In viewDidLoad. Afterwards, retain count is 1. _sushiTypes = [[NSArray alloc] initWithObjects:@"California Roll", @"Tuna Roll", @"Salmon Roll", @"Unagi Roll", @"Philadelphia Roll", @"Rainbow Roll", @"Vegetable Roll", @"Spider Roll", @"Shrimp Tempura Roll", @"Cucumber Roll", @"Yellowtail Roll", @"Spicy Tuna Roll", @"Avocado Roll", @"Scallop Roll", nil]; // In viewDidUnload and dealloc. Afterwards, retain count is 0. [_sushiTypes release]; _sushiTypes = nil;
對於_lastSushiSelected,它是在用戶選中table view的一行時被賦值的。它在兩個地方有release。一個是在賦值以前,還有一個是在dealloc時面,請看下面代碼:
[_lastSushiSelected release]; _lastSushiSelected = [sushiString retain]; // In dealloc [_sushiTypes release]; _sushiTypes = nil;
這種方法確定是可行的,可是,它須要你很認真的思考,每一次你給一個變量賦值的時候,都要認真考慮與之相關的內存問題。要不要先release後再賦值啊,要不要retain啊,總之,當變量一多,項目一大起來,各類內存問題就隨之而來了。
所以,接下來,我會向你介紹一種簡單的方法---使用property來管理內存。
若是你熟悉其它編程語言,好比java或者c#,於對getters和setters的概念確定不陌生。當你擁有一個_sushiTypes的實例變量的時候,你常常須要讓其它類的對象來訪問這個變量。可是,若是直接使用.號的方式去訪問不太好,它破壞了封裝性的原則,把類的實現爆露給外面的,編程大師說的。無論你信不信,反正我是信了。:)
所以,你須要一個方法,叫作 「getSushiTypes」(或者僅僅是 「sushiTypes」 ,這樣少打了3個字母),同時,還須要一個方法,叫作「setSushiTypes」.經過使用這兩個方法來訪問類的實例變量。這是一個好的編碼習慣,由於你能夠改變實例變量的名字,可是你不會影響到其它類,由於接口沒變。因此,咱們編碼代碼的時候,也要多針對接口編碼,少針對實現編碼。固然,使用getter和setter還有其它好處,你能夠在裏面用NSLog輸出一些內容,這樣你就知道有沒有人想窺探你的私有變量啦。至關於一個保鏢。
像上面我所說的那樣,爲每一個的的實例變量定義相應的getter和setter方法,固然,前提是你想讓外部訪問這個變量你才定義,你別搞得把所有變量都公開,那樣封裝的意義在哪裏呢?這樣,將會使內存管理的工做變得輕鬆。接下來,讓咱們看看,我是如何給這兩個變量添加getters和setters的。
首先,在RootViewController.h裏面,聲明下面四個方法:
- (NSArray *)sushiTypes; - (void)setSushiTypes:(NSArray *)sushiTypes; - (NSString *)lastSushiSelected; - (void)setLastSushiSelected:(NSString *)lastSushiSelected;
而後,在RootViewController.m底部添加其實現:
- (NSArray *)sushiTypes { return _sushiTypes; } - (void)setSushiTypes:(NSArray *)sushiTypes { [sushiTypes retain]; [_sushiTypes release]; _sushiTypes = sushiTypes; } - (NSString *)lastSushiSelected { return _lastSushiSelected; } - (void)setLastSushiSelected:(NSString *)lastSushiSelected { [lastSushiSelected retain]; [_lastSushiSelected release]; _lastSushiSelected = lastSushiSelected; }
這裏的getter方法很簡單,它們只是返回各自的變量而已。
而setter方法,首先把傳入的參數引用計數加1,同時把以前的實例變量引用計數減1,而後再把輸入的變量賦值給實例變量。(譯者:這裏的寫法其實很差,沒有考慮自賦值的狀況。若是你們也過C++的String類,那麼寫拷貝構造函數和賦值操做符的時候,是必定要考慮自賦值的狀況的,否則會出問題。可是,上面做者的寫法不會有問題。由於它先retain的,後release的。若是你寫反了,先release,那麼就出問題了。可是,若是我考慮自賦值的狀況,那麼我就不用考慮這種前後順序的問題了。具體寫法請參照個人原創,objc @property詳解)。經過這種方式,新傳入的參數被實例變量所引用,由於是全部者,因此符合「誰擁有,誰retain」的原則。
你可能會奇怪,爲何setter方法要先調用retain/release,而後再賦值,並且順序不能變。固然啦,確定是防止自賦值的狀況啦。若是你仍是搞不懂,那就算了吧,有些事情,總有一天你會明白的。:)
注意,這裏爲何要把實例變量的命名前面加一個下劃線呢?這樣作一是可使得getter和setter方法的參數命名得到方便。若是咱們的實例變量命名爲 「sushiTypes」,那麼咱們的setSushiTypes函數的參數名就不能再是「sushiTypes」,由於那會引發衝突,編譯會報錯的。同時,若是你把全部的實例變量都加一個下劃線,你的同事看你的代碼的時候,也立刻就知道,這是一個實例變量,我使用時得當心。固然,還有apple的kvc和kvo機制,也靠下劃線去搜索key,具體我不展開說了,看書吧。
最後,注意,這裏的getter和setter方法不是線程安全的,可是,對於本應用程序來講,getter和setter方法只會在主線程裏面訪問,因此「線程安全不安全」,跟咱不要緊!
如今,你有新的getter和setter了,修改本類中的其它代碼,開始使用getter和setter吧。讓咱們先從sushiTypes開始:
// In viewDidLoad self.sushiTypes = [[[NSArray alloc] initWithObjects:@"California Roll", @"Tuna Roll", @"Salmon Roll", @"Unagi Roll", @"Philadelphia Roll", @"Rainbow Roll", @"Vegetable Roll", @"Spider Roll", @"Shrimp Tempura Roll", @"Cucumber Roll", @"Yellowtail Roll", @"Spicy Tuna Roll", @"Avocado Roll", @"Scallop Roll", nil] autorelease]; // In viewDidUnload and dealloc self.sushiTypes = nil;
調用「self.sushiTypes = xxx」和調用 「[self setSushiTypes:xxx]」,這二者徹底等價--這裏的「.」號,對於我來講,就是「好看」而已。
所以,咱們不是去直接訪問_sushiTypes實例變量,而是用setter來設置它的值。回想一下,setter會把傳入的對數引用計數加1.所以,如今,咱們不能直接把alloc/init獲得的值直接賦給_sushiTypes了(由於,咱們經過setter訪問的時候,那這個alloc/init建立的變量的引用計數會是2,那會有問題,由於它的全部者只有一個,那意味着,在未來,只會被全部者release一次。可是,此時引用計數仍是1,永遠也不會被釋放掉了。也就是說,恭喜你!內存泄露了!)因此,在調用alloc/init以後,咱們還須要調用一下autorelease。
在viewDidUnload和dealloc方法裏同,咱們再也不是手動地relase再設置爲nil了。咱們只須要使用setter,一句設計self.xxx = nil就搞定。若是你用self._sushiTypes = nil的話,那麼會生成下列的代碼:
[nil retain]; // Does nothing [_sushiTypes release]; _sushiTypes = nil;
順便說一下,給您提個醒----一些人可能跟你說過「永遠不要在init或者dealloc方法裏面使用getter或者setter方法」。他們這樣說是爲何呢?由於,若是你在alloc或者dealloc函數裏面使用setter或getter,可是,它的子類重寫了getter和setter,由於objc全部的方法都是「虛方法」,也就是說能夠被重寫。那麼子類init方法調用[(self = [super init]))的時候,先調父類的init方法,而裏面使用了getter和setter,而正好這兩個方法又被你覆蓋了,若是你在這兩個覆蓋的方法裏面幹了一些事,那麼就會有問題了。仔細想一想,爲何!由於,你的子類尚未初使化完畢啊!!!你如今還在調用父類的「構造函數」,可是,你已經使用了子類的方法!!!可是,我想說的是,我在這裏違反了某些人提供的原則。爲何,由於我知道會可能有反作用。因此,我不會輕易重載父類的getter或setter方法。咱們這裏這樣寫,能夠幫助咱們簡化代碼。這固然是我我的意見,僅供參考。
如今,修改代碼,讓lastSushiSelected也使用setter方法來賦值:
self.lastSushiSelected = sushiString; // In dealloc self.lastSushiSelected = nil;
哇---如今,咱們對於內存問題的擔憂少了不少了,不是嗎?你沒有開動腦筋使勁想,哪裏須要retain啊,哪些須要release啊。這裏的setter方法,它在某種程度上替你完成了內存管理的工做。
所以,寫getter和setter能夠方便其它類訪問你類裏面的實例變量,同時,有時候也會使你的內存管理工做變得更加輕鬆。
可是,一遍又一遍地寫一大堆這些getter和setter方法,那麼我會瘋掉的。搞java的爲何沒瘋?由於eclipse自動能夠生成。搞c++的爲何也沒瘋,由於,能夠直接public。可是,objc 的@public是沒用的。不用擔憂,沒有人會想一遍又一遍地乾重復的事情的,因此objc2.0提供了一個新的,很是有用的特性,叫作@property,也叫屬性。
咱們能夠把咱們前面寫的那些getter和setter方法所有註釋掉,只須要寫上下面兩行代碼就夠了。本身動手試一下吧,打開RootViewController.h,而後找到getter和setter聲明的地方,把它換成下面的2行代碼:
@property (nonatomic, retain) NSArray * sushiTypes; @property (nonatomic, retain) NSString * lastSushiSelected;
這是使用屬性的第一步,建立屬性聲明。
屬性的聲明以@property關鍵字開始,而後在括號裏面傳入一些參數(好比atomic/nonatomic/assign/copy/retain等)。最後,你指明瞭屬性的類型和名字。
propetyceov是一些特殊的關鍵字,它能夠告訴編譯器如何生成getter和setter。這裏,你指定了兩個參數,一個是nonatomic,它是告訴編譯器,你不用擔憂多線程的問題。還有一個是reatin,它是告訴編譯器,在把setter參數傳給實例變量以前,要先retain一下。
在其它狀況下,你可能想使用「assign」參數,而不是reatin,「assign」告訴編譯器不要retain傳入的參數。或者,有時你還須要指定「copy」參數,它會在setter參數賦值給實例變量以前,先copy一下。
好了,爲了完成property的使用,你先轉到RootViewController.m,刪除以前寫的getter和setter,而後在文件的頂部添加下面2行代碼:
@synthesize sushiTypes = _sushiTypes; @synthesize lastSushiSelected = _lastSushiSelected;
上面的代碼是告訴編譯器,請你基於我前面定義的property及其參數,請爲我生成相應的getter和setter方法。你使用@synthesize關鍵字開始,而後給出屬性名字,(若是屬性名字和實例變量名字不同的話),那麼必定要寫上=於號,這樣在生成setter方法的時候,編譯才知道,傳入的參數要賦值給誰。切記一次要寫上等於號!!!若是實例變量名和屬性名同樣,那就沒必須了。
就這麼多!編譯並運行代碼吧,同樣很ok,運行得很好。可是,和以前你寫的那堆代碼相比較,是否是更容易理解了呢?同時也會使得出錯的機率降低。
到目前爲止,你應該了角propety的用法,以及具體是如何工做的吧!接下來,我將給出一些使用property的建議。
我想在這篇教程裏面添加一些這樣的策略,由於,它可以幫助我在管理objc內存的時候,更加輕鬆,更加不容易犯錯誤。
若是你遵照這些規則的話,那麼,在大部分時候,你會遠離內存相關問題的煩惱。固然,盲目地背下這些規則而不去理解,爲何會有這些規則,爲何這些規則就可以有做用。這確定是不行的啦!可是,若是你是新手的話,你能夠按照我給的這些規則去作,這樣你會避免大量的內存相關的錯誤,使得你的平常編程活動更加輕鬆。
我先把這些規則一條條列出來,接下來,我再詳細依個討論。
好,如今開始逐條討論!
規則1:經過爲每個實例變量定義property,你可讓編譯器爲你寫內存相關的代碼。缺點很明顯了,你破壞了類的封裝性,這樣的話,可能會使你的類的耦合度變得更高,更不利用維護和代碼複用。
規則2:經過把類的property參數指明爲retain,那麼在任什麼時候候,你能夠均可以訪問它們。大家你還保存有它們的一個引用計數,內存不會被釋放掉的,你是擁有者,你負責釋放。
規則3:當你建立一個類對象的時候,使用alloc/init/autorelease慣用法(就像你以前建立sushiTypes的數組那樣的)。這樣的話,內存會被自動釋放。若是你想讓它不釋放的話,那麼在要賦值的實例變量,在聲明其property的參數那裏,聲明一個retain吧。
規則4:無論何時給實例變量賦值,都使用 self.xxx的語法,這樣的話,當你給實例變量賦值的時候,會先把老的值釋放掉,而且retain新的變量值。注意,有些程序員擔憂在init和dealloc函數裏面使用getter和setter函數會帶來反作用,可是,我認爲這沒什麼。只要你對內存管理規則徹底清楚,你不會去作「在子類裏重寫父類的getter和setter」方法的事的。或者,就算實際須要,必須重寫父類的getter和setter,你也會十分注意,不在重寫的過程當中,給代碼帶來任何反作用的,對吧?
規則5:在dealloc函數裏面,使用「self.xxx = nil」 ,這樣能夠經過property使用其引用計數減1.不用要忘了還有viewDidUnload!
我知道,如今個人博客上有一大批cocos2d的忠實粉絲,所以,接下來的tips是特地爲大家準備的!
上面提出的5點規則,對於coocs2d來講,有點太嚴格了,或者直接說,太死了。由於,大部分時候,咱們的對象都加到層裏面去了,咱們在類裏面定義一些實例變量,僅僅是爲了在除init方法以外的其餘方法裏面方法使用。(其實,不少人喜歡定義tag,而後在addChild的時候指定一個tag,而後在其餘方法裏同,使用[self getChildByTag:xxx]來得到你想要的對象。由於層裏面有個CCArray的數組,它用來保存層的全部孩子結點,當調用addChild的時候,實際上是調用CCArray的addObject方法,因此,加到層裏面的孩子,其引用計數會加1.
所以,爲了不定義一些沒必要要的property,下面是我對於cocos2d的使用者的一些建議:
我我的以爲用上面4條方法來開發cocos2d遊戲,感受還不錯,簡單,快捷。
注意,若是某個對象沒有加到當前層裏面去,好比action。那麼,就不會使用上面4個規則了。手動去reatin/release吧。
記住,規則是死的,人是活的!只要理解了objc的內存管理規則,你能夠忘記上面全部的規則!
這裏有本教程的完整源代碼。
若是你還對property或者內存管理方面有任何疑問的話,請留言。固然,若是各位看觀有什麼好的,關於內存管理的小訣竅,小技巧,也歡迎提出來,分享一下,在下十分感激!
目前爲止,關於objc的內存管理系列教程就所有結束啦。真心但願,經過翻譯這3篇教程,以及我本身寫的那一篇教程,可以幫助你們走出objc內存管理的泥潭。
若是有什麼好的意見或者建議,請在下方留言。若是您想但願獲得哪方面的教程,或者你對哪方面還不太熟悉,也請留言。雖然我很忙(其實你們都很忙:)),可是,我有時間的時候,仍是會盡力知足你們的要求的。
再次謝謝您的閱讀,下篇教程見!