(译)在Objective-c里面使用property教程
免責(zé)申明(必讀!):本博客提供的所有教程的翻譯原稿均來(lái)自于互聯(lián)網(wǎng),僅供學(xué)習(xí)交流之用,切勿進(jìn)行商業(yè)傳播。同時(shí),轉(zhuǎn)載時(shí)不要移除本申明。如產(chǎn)生任何糾紛,均與本博客所有人、發(fā)表該翻譯稿之人無(wú)任何關(guān)系。謝謝合作!
原文鏈接地址:http://www.raywenderlich.com/2712/using-properties-in-objective-c-tutorial
教程截圖:
這是在iphone上面使用objc,與內(nèi)存管理有關(guān)的第三篇教程。
在第一篇教程中,我們介紹了在objective-c里面如果使用實(shí)例變量和引用計(jì)數(shù)來(lái)管理內(nèi)存。
在第二篇教程中,我們介紹了如何檢測(cè)內(nèi)存泄露、與內(nèi)存有關(guān)的易犯的錯(cuò)誤,使用是Instruments以及其它輔助工具。
在這第三篇教程中,也是本系列的最后一篇教程,我們將談一談objc的property。我們將介紹property是什么,它是怎樣工作的,有一些什么樣的規(guī)則,以及使用它們可以用來(lái)避免大部分與內(nèi)存相關(guān)的問(wèn)題。
如果你還沒(méi)有本系列教程的樣例工程的話,可以點(diǎn)擊這里下載,我們將從這個(gè)工程開始。
Retain Your Memory
讓我們先回顧一下,本項(xiàng)目需要管理內(nèi)存的地方在哪。
目前RootViewController有兩個(gè)實(shí)例變量:_sushiTypes, 和 _lastSushiSelected.
@interface RootViewController : UITableViewController {NSArray * _sushiTypes;
NSString * _lastSushiSelected;
}
@end
對(duì)于_sushiTypes,我們是在viewDidLoad里面通過(guò)alloc/init的方式來(lái)創(chuàng)建的,然后在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;
對(duì)于_lastSushiSelected,它是在用戶選中table view的一行時(shí)被賦值的。它在兩個(gè)地方有release。一個(gè)是在賦值之前,還有一個(gè)是在dealloc時(shí)面,請(qǐng)看下面代碼:
[_lastSushiSelected release];_lastSushiSelected = [sushiString retain];
// In dealloc
[_sushiTypes release];
_sushiTypes = nil;
這種方法肯定是可行的,但是,它需要你很認(rèn)真的思考,每一次你給一個(gè)變量賦值的時(shí)候,都要認(rèn)真考慮與之相關(guān)的內(nèi)存問(wèn)題。要不要先release后再賦值啊,要不要retain啊,總之,當(dāng)變量一多,項(xiàng)目一大起來(lái),各種內(nèi)存問(wèn)題就隨之而來(lái)了。
因此,接下來(lái),我會(huì)向你介紹一種簡(jiǎn)單的方法---使用property來(lái)管理內(nèi)存。
搬把椅子過(guò)來(lái),開始編碼吧
如果你熟悉其它編程語(yǔ)言,比如java或者c#,于對(duì)getters和setters的概念肯定不陌生。當(dāng)你擁有一個(gè)_sushiTypes的實(shí)例變量的時(shí)候,你經(jīng)常需要讓其它類的對(duì)象來(lái)訪問(wèn)這個(gè)變量。但是,如果直接使用.號(hào)的方式去訪問(wèn)不太好,它破壞了封裝性的原則,把類的實(shí)現(xiàn)爆露給外面的,編程大師說(shuō)的。不管你信不信,反正我是信了。:)
因此,你需要一個(gè)方法,叫做?“getSushiTypes”(或者僅僅是?“sushiTypes” ,這樣少打了3個(gè)字母),同時(shí),還需要一個(gè)方法,叫做“setSushiTypes”.通過(guò)使用這兩個(gè)方法來(lái)訪問(wèn)類的實(shí)例變量。這是一個(gè)好的編碼習(xí)慣,因?yàn)槟憧梢愿淖儗?shí)例變量的名字,但是你不會(huì)影響到其它類,因?yàn)榻涌跊](méi)變。所以,我們編碼代碼的時(shí)候,也要多針對(duì)接口編碼,少針對(duì)實(shí)現(xiàn)編碼。當(dāng)然,使用getter和setter還有其它好處,你可以在里面用NSLog輸出一些內(nèi)容,這樣你就知道有沒(méi)有人想窺探你的私有變量啦。相當(dāng)于一個(gè)保鏢。
像上面我所說(shuō)的那樣,為每個(gè)的的實(shí)例變量定義相應(yīng)的getter和setter方法,當(dāng)然,前提是你想讓外部訪問(wèn)這個(gè)變量你才定義,你別搞得把全部變量都公開,那樣封裝的意義在哪里呢?這樣,將會(huì)使內(nèi)存管理的工作變得輕松。接下來(lái),讓我們看看,我是如何給這兩個(gè)變量添加getters和setters的。
首先,在RootViewController.h里面,聲明下面四個(gè)方法:
- (NSArray *)sushiTypes;- (void)setSushiTypes:(NSArray *)sushiTypes;
- (NSString *)lastSushiSelected;
- (void)setLastSushiSelected:(NSString *)lastSushiSelected;
然后,在RootViewController.m底部添加其實(shí)現(xiàn):
- (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方法很簡(jiǎn)單,它們只是返回各自的變量而已。
而setter方法,首先把傳入的參數(shù)引用計(jì)數(shù)加1,同時(shí)把之前的實(shí)例變量引用計(jì)數(shù)減1,然后再把輸入的變量賦值給實(shí)例變量。(譯者:這里的寫法其實(shí)不好,沒(méi)有考慮自賦值的情況。如果大家也過(guò)C++的String類,那么寫拷貝構(gòu)造函數(shù)和賦值操作符的時(shí)候,是一定要考慮自賦值的情況的,不然會(huì)出問(wèn)題。但是,上面作者的寫法不會(huì)有問(wèn)題。因?yàn)樗萺etain的,后release的。如果你寫反了,先release,那么就出問(wèn)題了。但是,如果我考慮自賦值的情況,那么我就不用考慮這種先后順序的問(wèn)題了。具體寫法請(qǐng)參照我的原創(chuàng),objc @property詳解)。通過(guò)這種方式,新傳入的參數(shù)被實(shí)例變量所引用,因?yàn)槭撬姓?#xff0c;所以符合“誰(shuí)擁有,誰(shuí)retain”的原則。
你可能會(huì)奇怪,為什么setter方法要先調(diào)用retain/release,然后再賦值,而且順序不能變。當(dāng)然啦,肯定是防止自賦值的情況啦。如果你還是搞不懂,那就算了吧,有些事情,總有一天你會(huì)明白的。:)
注意,這里為什么要把實(shí)例變量的命名前面加一個(gè)下劃線呢?這樣做一是可以使得getter和setter方法的參數(shù)命名獲得方便。如果我們的實(shí)例變量命名為?“sushiTypes”,那么我們的setSushiTypes函數(shù)的參數(shù)名就不能再是“sushiTypes”,因?yàn)槟菚?huì)引起沖突,編譯會(huì)報(bào)錯(cuò)的。同時(shí),如果你把所有的實(shí)例變量都加一個(gè)下劃線,你的同事看你的代碼的時(shí)候,也馬上就知道,這是一個(gè)實(shí)例變量,我使用時(shí)得小心。當(dāng)然,還有apple的kvc和kvo機(jī)制,也靠下劃線去搜索key,具體我不展開說(shuō)了,看書吧。
最后,注意,這里的getter和setter方法不是線程安全的,但是,對(duì)于本應(yīng)用程序來(lái)說(shuō),getter和setter方法只會(huì)在主線程里面訪問(wèn),所以“線程安全不安全”,跟咱沒(méi)關(guān)系!
現(xiàn)在,你地基有了,開干吧!
現(xiàn)在,你有新的getter和setter了,修改本類中的其它代碼,開始使用getter和setter吧。讓我們先從sushiTypes開始:
// In viewDidLoadself.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;
調(diào)用“self.sushiTypes = xxx”和調(diào)用?“[self setSushiTypes:xxx]”,這兩者完全等價(jià)--這里的“.”號(hào),對(duì)于我來(lái)說(shuō),就是“好看”而已。
因此,我們不是去直接訪問(wèn)_sushiTypes實(shí)例變量,而是用setter來(lái)設(shè)置它的值。回想一下,setter會(huì)把傳入的對(duì)數(shù)引用計(jì)數(shù)加1.因此,現(xiàn)在,我們不能直接把a(bǔ)lloc/init得到的值直接賦給_sushiTypes了(因?yàn)?#xff0c;我們通過(guò)setter訪問(wèn)的時(shí)候,那這個(gè)alloc/init創(chuàng)建的變量的引用計(jì)數(shù)會(huì)是2,那會(huì)有問(wèn)題,因?yàn)樗乃姓咧挥幸粋€(gè),那意味著,在將來(lái),只會(huì)被所有者release一次。但是,此時(shí)引用計(jì)數(shù)還是1,永遠(yuǎn)也不會(huì)被釋放掉了。也就是說(shuō),恭喜你!內(nèi)存泄露了!)所以,在調(diào)用alloc/init之后,我們還需要調(diào)用一下autorelease。
在viewDidUnload和dealloc方法里同,我們不再是手動(dòng)地relase再設(shè)置為nil了。我們只需要使用setter,一句設(shè)計(jì)self.xxx = nil就搞定。如果你用self._sushiTypes = nil的話,那么會(huì)生成下列的代碼:
[nil retain]; // Does nothing[_sushiTypes release];
_sushiTypes = nil;
順便說(shuō)一下,給您提個(gè)醒----一些人可能跟你說(shuō)過(guò)“永遠(yuǎn)不要在init或者dealloc方法里面使用getter或者setter方法”。他們這樣說(shuō)是為什么呢?因?yàn)?#xff0c;如果你在alloc或者dealloc函數(shù)里面使用setter或getter,但是,它的子類重寫了getter和setter,因?yàn)閛bjc所有的方法都是“虛方法”,也就是說(shuō)可以被重寫。那么子類init方法調(diào)用[(self = [super init]))的時(shí)候,先調(diào)父類的init方法,而里面使用了getter和setter,而正好這兩個(gè)方法又被你覆蓋了,如果你在這兩個(gè)覆蓋的方法里面干了一些事,那么就會(huì)有問(wèn)題了。仔細(xì)想想,為什么!因?yàn)?#xff0c;你的子類還沒(méi)有初使化完畢啊!!!你現(xiàn)在還在調(diào)用父類的“構(gòu)造函數(shù)”,但是,你已經(jīng)使用了子類的方法!!!但是,我想說(shuō)的是,我在這里違反了某些人提供的原則。為什么,因?yàn)槲抑罆?huì)可能有副作用。所以,我不會(huì)輕易重載父類的getter或setter方法。我們這里這樣寫,可以幫助我們簡(jiǎn)化代碼。這當(dāng)然是我個(gè)人意見,僅供參考。
現(xiàn)在,修改代碼,讓lastSushiSelected也使用setter方法來(lái)賦值:
self.lastSushiSelected = sushiString;// In dealloc
self.lastSushiSelected = nil;
哇---現(xiàn)在,我們對(duì)于內(nèi)存問(wèn)題的擔(dān)心少了很多了,不是嗎?你沒(méi)有開動(dòng)腦筋使勁想,哪里需要retain啊,哪些需要release啊。這里的setter方法,它在某種程度上替你完成了內(nèi)存管理的工作。
一個(gè)簡(jiǎn)單的建議
因此,寫getter和setter可以方便其它類訪問(wèn)你類里面的實(shí)例變量,同時(shí),有時(shí)候也會(huì)使你的內(nèi)存管理工作變得更加輕松。
但是,一遍又一遍地寫一大堆這些getter和setter方法,那么我會(huì)瘋掉的。搞java的為什么沒(méi)瘋?因?yàn)閑clipse自動(dòng)可以生成。搞c++的為什么也沒(méi)瘋,因?yàn)?#xff0c;可以直接public。但是,objc 的@public是沒(méi)用的。不用擔(dān)心,沒(méi)有人會(huì)想一遍又一遍地干重復(fù)的事情的,所以objc2.0提供了一個(gè)新的,非常有用的特性,叫做@property,也叫屬性。
我們可以把我們前面寫的那些getter和setter方法全部注釋掉,只需要寫上下面兩行代碼就夠了。自己動(dòng)手試一下吧,打開RootViewController.h,然后找到getter和setter聲明的地方,把它換成下面的2行代碼:
@property (nonatomic, retain) NSArray * sushiTypes;@property (nonatomic, retain) NSString * lastSushiSelected;
這是使用屬性的第一步,創(chuàng)建屬性聲明。
屬性的聲明以@property關(guān)鍵字開始,然后在括號(hào)里面?zhèn)魅胍恍﹨?shù)(比如atomic/nonatomic/assign/copy/retain等)。最后,你指明了屬性的類型和名字。
propetyceov是一些特殊的關(guān)鍵字,它可以告訴編譯器如何生成getter和setter。這里,你指定了兩個(gè)參數(shù),一個(gè)是nonatomic,它是告訴編譯器,你不用擔(dān)心多線程的問(wèn)題。還有一個(gè)是reatin,它是告訴編譯器,在把setter參數(shù)傳給實(shí)例變量之前,要先retain一下。
在其它情況下,你可能想使用“assign”參數(shù),而不是reatin,“assign”告訴編譯器不要retain傳入的參數(shù)。或者,有時(shí)你還需要指定“copy”參數(shù),它會(huì)在setter參數(shù)賦值給實(shí)例變量之前,先copy一下。
好了,為了完成property的使用,你先轉(zhuǎn)到RootViewController.m,刪除之前寫的getter和setter,然后在文件的頂部添加下面2行代碼:
@synthesize sushiTypes = _sushiTypes;@synthesize lastSushiSelected = _lastSushiSelected;
上面的代碼是告訴編譯器,請(qǐng)你基于我前面定義的property及其參數(shù),請(qǐng)為我生成相應(yīng)的getter和setter方法。你使用@synthesize關(guān)鍵字開始,然后給出屬性名字,(如果屬性名字和實(shí)例變量名字不一樣的話),那么一定要寫上=于號(hào),這樣在生成setter方法的時(shí)候,編譯才知道,傳入的參數(shù)要賦值給誰(shuí)。切記一次要寫上等于號(hào)!!!如果實(shí)例變量名和屬性名一樣,那就沒(méi)必須了。
就這么多!編譯并運(yùn)行代碼吧,一樣很ok,運(yùn)行得很好。但是,和之前你寫的那堆代碼相比較,是不是更容易理解了呢?同時(shí)也會(huì)使得出錯(cuò)的概率下降。
到目前為止,你應(yīng)該了角propety的用法,以及具體是如何工作的吧!接下來(lái),我將給出一些使用property的建議。
一般性的策略
我想在這篇教程里面添加一些這樣的策略,因?yàn)?#xff0c;它能夠幫助我在管理objc內(nèi)存的時(shí)候,更加輕松,更加不容易犯錯(cuò)誤。
如果你遵守這些規(guī)則的話,那么,在大部分時(shí)候,你會(huì)遠(yuǎn)離內(nèi)存相關(guān)問(wèn)題的煩惱。當(dāng)然,盲目地背下這些規(guī)則而不去理解,為什么會(huì)有這些規(guī)則,為什么這些規(guī)則就能夠有作用。這肯定是不行的啦!但是,如果你是新手的話,你可以按照我給的這些規(guī)則去做,這樣你會(huì)避免大量的內(nèi)存相關(guān)的錯(cuò)誤,使得你的日常編程活動(dòng)更加輕松。
我先把這些規(guī)則一條條列出來(lái),接下來(lái),我再詳細(xì)依個(gè)討論。
好,現(xiàn)在開始逐條討論!
規(guī)則1:通過(guò)為每一個(gè)實(shí)例變量定義property,你可以讓編譯器為你寫內(nèi)存相關(guān)的代碼。缺點(diǎn)很明顯了,你破壞了類的封裝性,這樣的話,可能會(huì)使你的類的耦合度變得更高,更不利用維護(hù)和代碼復(fù)用。
規(guī)則2:通過(guò)把類的property參數(shù)指明為retain,那么在任何時(shí)候,你可以都可以訪問(wèn)它們。你們你還保存有它們的一個(gè)引用計(jì)數(shù),內(nèi)存不會(huì)被釋放掉的,你是擁有者,你負(fù)責(zé)釋放。
規(guī)則3:當(dāng)你創(chuàng)建一個(gè)類對(duì)象的時(shí)候,使用alloc/init/autorelease慣用法(就像你之前創(chuàng)建sushiTypes的數(shù)組那樣的)。這樣的話,內(nèi)存會(huì)被自動(dòng)釋放。如果你想讓它不釋放的話,那么在要賦值的實(shí)例變量,在聲明其property的參數(shù)那里,聲明一個(gè)retain吧。
規(guī)則4:不管什么時(shí)候給實(shí)例變量賦值,都使用?self.xxx的語(yǔ)法,這樣的話,當(dāng)你給實(shí)例變量賦值的時(shí)候,會(huì)先把老的值釋放掉,并且retain新的變量值。注意,有些程序員擔(dān)心在init和dealloc函數(shù)里面使用getter和setter函數(shù)會(huì)帶來(lái)副作用,但是,我認(rèn)為這沒(méi)什么。只要你對(duì)內(nèi)存管理規(guī)則完全清楚,你不會(huì)去做“在子類里重寫父類的getter和setter”方法的事的。或者,就算實(shí)際需要,必須重寫父類的getter和setter,你也會(huì)十分注意,不在重寫的過(guò)程中,給代碼帶來(lái)任何副作用的,對(duì)吧?
規(guī)則5:在dealloc函數(shù)里面,使用“self.xxx = nil” ,這樣可以通過(guò)property使用其引用計(jì)數(shù)減1.不用要忘了還有viewDidUnload!
與cocos2d有關(guān)的簡(jiǎn)單策略
我知道,現(xiàn)在我的博客上有一大批cocos2d的忠實(shí)粉絲,因此,接下來(lái)的tips是特意為你們準(zhǔn)備的!
上面提出的5點(diǎn)規(guī)則,對(duì)于coocs2d來(lái)說(shuō),有點(diǎn)太嚴(yán)格了,或者直接說(shuō),太死了。因?yàn)?#xff0c;大部分時(shí)候,我們的對(duì)象都加到層里面去了,我們?cè)陬惱锩娑x一些實(shí)例變量,僅僅是為了在除init方法之外的其他方法里面方法使用。(其實(shí),很多人喜歡定義tag,然后在addChild的時(shí)候指定一個(gè)tag,然后在其他方法里同,使用[self getChildByTag:xxx]來(lái)獲得你想要的對(duì)象。因?yàn)閷永锩嬗袀€(gè)CCArray的數(shù)組,它用來(lái)保存層的所有孩子結(jié)點(diǎn),當(dāng)調(diào)用addChild的時(shí)候,其實(shí)是調(diào)用CCArray的addObject方法,所以,加到層里面的孩子,其引用計(jì)數(shù)會(huì)加1.
因此,為了避免定義一些不必要的property,下面是我對(duì)于cocos2d的使用者的一些建議:
我個(gè)人覺(jué)得用上面4條方法來(lái)開發(fā)cocos2d游戲,感覺(jué)還不錯(cuò),簡(jiǎn)單,快捷。
注意,如果某個(gè)對(duì)象沒(méi)有加到當(dāng)前層里面去,比如action。那么,就不會(huì)使用上面4個(gè)規(guī)則了。手動(dòng)去reatin/release吧。
記住,規(guī)則是死的,人是活的!只要理解了objc的內(nèi)存管理規(guī)則,你可以忘記上面所有的規(guī)則!
何去何從?
這里有本教程的完整源代碼。
如果你還對(duì)property或者內(nèi)存管理方面有任何疑問(wèn)的話,請(qǐng)留言。當(dāng)然,如果各位看觀有什么好的,關(guān)于內(nèi)存管理的小訣竅,小技巧,也歡迎提出來(lái),分享一下,在下十分感激!
目前為止,關(guān)于objc的內(nèi)存管理系列教程就全部結(jié)束啦。真心希望,通過(guò)翻譯這3篇教程,以及我自己寫的那一篇教程,能夠幫助大家走出objc內(nèi)存管理的泥潭。
如果有什么好的意見或者建議,請(qǐng)?jiān)谙路搅粞浴H绻胂M玫侥姆矫娴慕坛?#xff0c;或者你對(duì)哪方面還不太熟悉,也請(qǐng)留言。雖然我很忙(其實(shí)大家都很忙:)),但是,我有時(shí)間的時(shí)候,還是會(huì)盡力滿足大家的要求的。
再次謝謝您的閱讀,下篇教程見!
著作權(quán)聲明:本文由http://www.cnblogs.com/andyque翻譯,歡迎轉(zhuǎn)載分享。請(qǐng)尊重作者勞動(dòng),轉(zhuǎn)載時(shí)保留該聲明和作者博客鏈接,謝謝!
轉(zhuǎn)載于:https://www.cnblogs.com/zilongshanren/archive/2011/08/08/2123993.html
總結(jié)
以上是生活随笔為你收集整理的(译)在Objective-c里面使用property教程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【转】Hibernate数据过滤
- 下一篇: ExtJS 4.1有什么值得期待?