屬性( property)是 Objecive-C的一項特性,用于封裝對象中的數(shù)據(jù)嵌戈。 Objective-C對象通常會把其所需要的數(shù)據(jù)保存為各種實例變量抢呆。
實例變量一般通過“存取方法”(acssmethod)來訪問漓糙。其中,“獲取方法”( getter)用于讀取變量值,而“設(shè)置方法”(setter)用于寫入變量值。
例如:
在描述個人信息的類中,也許會存放人名棒仍、生日捉兴、地址等內(nèi)容÷砥В可以在類接口的 public區(qū)段中聲明一些實例變量:
@interface EOCPerson : NSObject {
@public
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
這種寫法的問題是:對象布局在編譯期就已經(jīng)固定了庄拇。只要碰到訪問 _firsTname變量的代碼,編譯器就把其替換為“偏移量”( offset),這個偏移量是“硬編碼”( hardcode),表示該變量距離存放對象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)。
@interface EOCPerson : NSObject {
@public
NSDate *_dateOfBirth;
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
解決辦法:
應(yīng)用程序二進(jìn)制接口
Objective-C引入了“應(yīng)用程序二進(jìn)制接口”(Application Binary Interface韭邓,ABI)措近。
這種做法是,把實例變量當(dāng)作一種存儲偏移量所用的“特殊變量”女淑,交由“類對象”保管瞭郑。偏移量會在運行期查找,如果類的定義變了鸭你,那么存儲的偏移量也就變了屈张,這樣的話擒权,無論何時訪問實例變量,總能使用正確的偏移量阁谆,甚至可以在運行期向類中新增實例變量碳抄。
總結(jié):可以在“class-continuation分類”或?qū)崿F(xiàn)文件中定義實例變量。所以场绿,不一定要在接口把全部實例變量都聲明好剖效,可以將某些變量從接口的public區(qū)段里移走,以便保護(hù)與類實現(xiàn)有關(guān)的內(nèi)部信息焰盗。
另外一種解決辦法
盡量不要直接訪問實例變量璧尸,而應(yīng)該通過存取方法來做。但是姨谷,存取方法有著嚴(yán)格的命名規(guī)范逗宁,所以,Objective-C引入了屬性@property語法梦湘,讓編譯器自動編寫存取方法瞎颗。當(dāng)然,開發(fā)者也可以自己編寫存取方法捌议。
屬性@property
屬性(property)哼拔,能夠訪問封裝在對象里的數(shù)據(jù)。開發(fā)者可以把屬性當(dāng)做一種簡稱瓣颅,其意思是說:編譯器會自動編寫一套存取方法倦逐,用以訪問給定類型中具有給定名稱的實例變量。
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
//等同于
@interface EOCPerson : NSObject
- (NSString*) firstName;
- (void)setFirstName : (NSString*)firstName;
- (NSString*)lastName;
- (void)setLastName : (NSString*)lastName;
@end
- 訪問屬性宫补,可以使用“點語法”檬姥,編譯器會把“點語法”轉(zhuǎn)換為對存取方法的調(diào)用,使用“點語法”的效果與直接調(diào)用存取方法相同粉怕。
- 如果使用了屬性的話健民,那么編譯器就會自動編寫訪問這些屬性所需的存取方法,還會自動向類添加適當(dāng)類型的實例變量(在屬性名稱前面加“_”用作實例變量的名稱)贫贝,此過程叫做“自動合成”(autosynthesis)秉犹。
@synthesis語法
通過@synthesis語法可以直接指定實例變量的名字,但一般情況下無須修改默認(rèn)的實例變量名稚晚。
@synthesis firstName = _myFirstName;
@dynamic語法
使用@dynamic關(guān)鍵字崇堵,它會告訴編譯器:不要自動創(chuàng)建實現(xiàn)屬性所用的實例變量,也不要為其創(chuàng)建存取方法客燕。而且鸳劳,在編譯訪問屬性的代碼時,即時編譯器發(fā)現(xiàn)沒有定義存取方法幸逆,也不會報錯棍辕,它相信這些方法能在運行期找到暮现。
@dynamic firstName, lastName;
屬性特質(zhì)
原子性
在默認(rèn)情況下,由編譯器所合成的方法會通過鎖定機制確保其原子性( atomicity)。如果屬性具備 nonatomic特質(zhì),則不使用同步鎖楚昭。請注意,盡管沒有名為“ atomic”的特質(zhì)(如果某屬性不具備 nonatomic特質(zhì),那它就是“原子的”( atomic)),但是仍然可以在屬性特質(zhì)中寫明這一點,編譯器不會報錯栖袋。若是自己定義存取方法,那么就應(yīng)該遵從與屬性特質(zhì)相符的原子性。
讀寫權(quán)限
- readwrite(讀寫)特質(zhì)的屬性擁有“獲取方法”(getter)與“設(shè)置方法”抚太。若該屬性由@ synthesize實現(xiàn),則編譯器會自動生成這兩個方法方法塘幅。
- readonly(只讀)特質(zhì)的屬性僅擁有獲取方法,只有當(dāng)該屬性由@ synthesize實現(xiàn)時,編譯器才會為其合成獲取方法。你可以用此特質(zhì)把某個屬性對外公開為只讀屬性,然后在“ class-continuation分類”中將其重新定義為讀寫屬性尿贫。
內(nèi)存管理語義
assign:設(shè)置方法”只會執(zhí)行針對“純量類型”( scalar type,例如 CGFloat或NSInteger等)的簡單賦值操作电媳。
strong:此特質(zhì)表明該屬性定義了一種“擁有關(guān)系”( owning relationship)。為這種屬性設(shè)置新值時,設(shè)置方法會先保留新值,并釋放舊值,然后再將新值設(shè)置上去庆亡。
weak:此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系”( nonowning relationship)匾乓。為這種屬性設(shè)置新值時,設(shè)置方法既不保留新值,也不釋放舊值。此特質(zhì)同 assign類似,然而在屬性所指的對象遭到摧毀時,屬性值也會清空( nil out)又谋。
unsafe_unretained:此特質(zhì)的語義和 assign相同,但是它適用于“對象類型"”( object type),該特質(zhì)表達(dá)一種“非擁有關(guān)系”(“不保留”, unretained),當(dāng)目標(biāo)對象遭到摧毀時,屬性值不會自動清空(“不安全”, unsafe,這一點與weak有區(qū)別拼缝。
copy:此特質(zhì)所表達(dá)的所屬關(guān)系與 strong類似。然而設(shè)置方法并不保留新值,而是將其“拷貝”(copy)彰亥。當(dāng)屬性類型為 NSString*時,經(jīng)常用此特質(zhì)來保護(hù)其封裝性,因為傳遞給設(shè)置方法的新值有可能指向一個 NSMutableString類的實例咧七。這個類是NSString的子類,表示一種可以修改其值的字符串,此時若是不拷貝字符串,那么設(shè)置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變”( immutable)的字符串,確保對象中的字符串值不會無意間變動任斋。只要實現(xiàn)屬性所用的對象是“可變的”( mutable),就應(yīng)該在設(shè)置新屬性值時拷貝一份继阻。
方法名
- getter=<name> :指定“獲取方法”的方法名。如果某屬性是Boolean型废酷,可以通過這個辦法為其獲取方法加上“is”前綴瘟檩。
- setter=<name> :指定“設(shè)置方法”的方法名。這種用法不常見澈蟆。
通過上述特質(zhì)芒帕,可以微調(diào)由編譯器所合成的存取方法。不過需要注意:若是自己來實現(xiàn)這些存取方法丰介,那么應(yīng)該保證其具備相關(guān)屬性所聲明的特質(zhì)。
例如鉴分,如果將某個屬性聲明為copy哮幢,那么就應(yīng)該在“設(shè)置方法”中拷貝相關(guān)對象,否則會誤導(dǎo)該屬性的使用者志珍,而且橙垢,若是不遵從這一約定,還會令程序產(chǎn)生bug伦糯。
/* 頭文件 */
@property (copy) NSString *firstName;
@property (copy) NSString *lastName;
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
/* 實現(xiàn)文件 */
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName{
if (self = [super init]){
// firstName有可能是NSMutableString*柜某,該字符串的值可能會在對象不知情的情況下遭人修改嗽元。所以,要拷貝一份“不可變”的字符串喂击,確保對象中的字符串值不會無意間變動剂癌。
_firstName = [firstName copy];
_lastName = [lastName copy];
}
return self;
}
盡量使用不可變的對象,也就是說翰绊,屬性應(yīng)該設(shè)為“只讀”佩谷。用初始化方法設(shè)置好屬性值之后,就不能再改變了监嗜。
@property (copy, readonly) NSString *firstName;
@property (copy, readonly) NSString *lastName;