可以用@property語(yǔ)法來(lái)定義對(duì)象中所封裝的數(shù)據(jù)
通過(guò)"特質(zhì)(arrribute)"來(lái)指定存儲(chǔ)數(shù)據(jù)所需的正確語(yǔ)義
在設(shè)置屬性所對(duì)應(yīng)的實(shí)例變量時(shí)胆剧,一定要遵從該屬性所聲明的語(yǔ)義
在開(kāi)發(fā)iOS程序時(shí)應(yīng)該使用nonatomic屬性挠他,因?yàn)閍tomic會(huì)嚴(yán)重影響性能
在對(duì)象內(nèi)部盡量直接訪問(wèn)實(shí)例變量
"屬性(property)"是Objective-C的一項(xiàng)特性礼搁,用于封裝對(duì)象中的數(shù)據(jù)。Objective-C對(duì)象通常會(huì)把其所需要的重要數(shù)據(jù)保存為各種實(shí)例變量濒旦。實(shí)例變量一般通過(guò)存取(getter和setter)方法來(lái)訪問(wèn)
在描述個(gè)人信息時(shí)娜庇,通過(guò)存放人名偎漫,生日爷恳,地址等內(nèi)容,可以在類接口的public區(qū)段聲明一些實(shí)例變量
@interface:NSObject{
@public
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someIntenalData;
}
@end
這種編寫(xiě)風(fēng)格類似于C++和Java象踊。但是Objective-C幾乎不會(huì)這樣做温亲,因?yàn)檫@樣的寫(xiě)法,對(duì)象布局在編譯器就已經(jīng)確定杯矩,當(dāng)要訪問(wèn)一個(gè)屬性的時(shí)候栈虚,使用的是硬編碼之后的偏移量(offset),表示該變量存放在對(duì)象起始地址有多遠(yuǎn)。如果要在_fisrtName前面再加上一個(gè)變量史隆,那么之前所指的偏移量都指向了錯(cuò)誤的地址魂务,修改之后必須重新編譯。
例如泌射,某個(gè)代碼庫(kù)中的代碼使用了一份舊的類定義粘姜。如果與之相連接的代碼使用了新的定義,那么運(yùn)行的時(shí)候就會(huì)出現(xiàn)不兼容的現(xiàn)象熔酷。各種語(yǔ)言均有相應(yīng)的解決方法.Objective-C的做法是把存儲(chǔ)偏移量所用的"特殊變量"交給類對(duì)象來(lái)保存孤紧。偏移量會(huì)在運(yùn)行時(shí)查找,如果類定義變了拒秘,那么存儲(chǔ)的偏移量也就變了号显,這樣可以保證何時(shí)訪問(wèn)實(shí)例變量,總能找到正確的偏移量躺酒。甚至可以在運(yùn)行期向類中新增實(shí)例變量押蚤,這就是穩(wěn)固的"應(yīng)用程序二進(jìn)制接口(Application Binary Interface,ABI)"
正是因?yàn)檫@些特性,使得我們可以在"類擴(kuò)展(class-continuation)"或?qū)崿F(xiàn)文件中定義實(shí)例變量羹应。所以說(shuō)揽碘,不一定要在接口中把全部實(shí)例變量都聲明好,可以將某些變量從接口的public區(qū)段移走,以便保護(hù)與類實(shí)現(xiàn)有關(guān)的內(nèi)部信息钾菊。
盡量不要對(duì)象外部直接訪問(wèn)實(shí)例變量,而應(yīng)該通過(guò)存取方法來(lái)做(因?yàn)檫\(yùn)行時(shí)機(jī)制)偎肃,可以使用@property
語(yǔ)法來(lái)編寫(xiě)實(shí)例變量煞烫,@property
會(huì)自動(dòng)生成對(duì)應(yīng)的存取方法。
例如:
@interface Person:NSObject
@property NSString *firstName;
@end
對(duì)于類的使用者來(lái)說(shuō)累颂,相當(dāng)于
//Person.h
@interface Person:NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString*)firstName;
@end
//Person.m
@implement Person
@synthesize _firstName;
@end
如果讀寫(xiě)用@property
定義的屬性滞详,可以使用點(diǎn)語(yǔ)法,編譯器會(huì)自動(dòng)將點(diǎn)語(yǔ)法轉(zhuǎn)換為對(duì)應(yīng)的存取方法;
Person *person = [[Person alloc]init];
NSString * temp = person.firstName;
person.firstName = @"Test";
編譯后會(huì)變成
Person *person = [[Person alloc]init];
NSString * temp = [person firstName];
[person setFirstName:@"Test"];
屬性特質(zhì)
使用屬性時(shí)還有一個(gè)問(wèn)題需要注意紊馏,就是其各種特質(zhì)(arrribute)設(shè)定也會(huì)影響編譯器所產(chǎn)生的存取方式料饥,屬性可以擁有的特質(zhì)分為以下四類
原子性
在默認(rèn)情況下,由編譯產(chǎn)生的合成方式會(huì)通過(guò)鎖定機(jī)制來(lái)確保其原子性(atomic)朱监,如果屬性具備nonatomic特質(zhì)岸啡,則不使用同步鎖。
如果開(kāi)發(fā)iOS應(yīng)用赫编,你會(huì)發(fā)現(xiàn)巡蘸,其中所有的屬性都聲明為nonatomic。這樣做是因?yàn)樵陂_(kāi)發(fā)iOS中使用同步鎖的開(kāi)銷較大擂送,這會(huì)帶來(lái)性能問(wèn)題悦荒,一般情況下并不要求屬性必須是"原子的",因?yàn)檫@并不能保證線程安全嘹吨,如果要實(shí)現(xiàn)線程安全的操作搬味,必須使用更深層次的鎖定機(jī)制才可以。也就是說(shuō)蟀拷,一個(gè)線程在連續(xù)多次讀取某屬性的過(guò)程中碰纬,有別的線程在同時(shí)改寫(xiě)該值,那么即使將屬性聲明為atomic问芬,也還是會(huì)讀到不同的屬性值嘀趟。因此在開(kāi)發(fā)iOS程序時(shí),一般都會(huì)使用nonatomic屬性愈诚。但是在開(kāi)發(fā)macOS程序的時(shí)候她按,使用atomic一般不會(huì)有性能瓶頸。
讀/寫(xiě)權(quán)限
- readwrite(讀寫(xiě))特質(zhì)的屬性擁有獲取(getter)方法和設(shè)置(setter)方法炕柔,這是默認(rèn)的讀寫(xiě)屬性酌泰,如果不指明,默認(rèn)就是readwrite
- readonly只讀匕累,只有讀取方法陵刹,沒(méi)有對(duì)外的設(shè)置方法
內(nèi)存管理語(yǔ)義
屬性用于封裝數(shù)據(jù),而數(shù)據(jù)則要有"具體的所有權(quán)語(yǔ)義"
- assign 設(shè)置方法只會(huì)針對(duì)"純量類型"(scalar type,例如CGFloat或者NSInteger等)進(jìn)行簡(jiǎn)單的賦值操作欢嘿,Objective-C的基本類型和所有在棧上存儲(chǔ)的變量使用這個(gè)特質(zhì)
- strong 表示擁有關(guān)系衰琐,強(qiáng)引用等同于retain
- weak 表示弱引用也糊,對(duì)象不擁有該屬性,對(duì)象遭到摧毀的時(shí)候羡宙,屬性值也會(huì)設(shè)為nil
- unsafe_unretained 該特質(zhì)語(yǔ)義和assign類似狸剃,但是它適用于"對(duì)象類型",該特質(zhì)表示一種非擁有關(guān)系,與weak的區(qū)別在于狗热,對(duì)象摧毀的時(shí)候钞馁,屬性值不會(huì)被設(shè)置為nil(unsafe)
- copy 此特質(zhì)表達(dá)所屬關(guān)系和strong類似,但是是保留一份拷貝的值匿刮,NSString一般要使用該特質(zhì)
方法名
@property默認(rèn)為屬性生成getter和setter方法名,如果需要自行制定getter和setter方法的方法名僧凰,可以使用該特質(zhì)
- getter = < name >:指定"獲取方法"的方法名,一般屬性是Boolean型熟丸,而你想為獲取方法加上"is"前綴训措,那么可以使用該特質(zhì)
@property (nonatomic,getter = isOn) BOOL on;
- **setter = < name > **:指定設(shè)置方法使用的方法名,不常用
屬性在對(duì)象內(nèi)部的訪問(wèn)
使用@property生成的變量光羞,在實(shí)現(xiàn)文件中會(huì)自動(dòng)生成一個(gè)以"_"開(kāi)頭的同名變量(見(jiàn)上述)隙弛。那么,在對(duì)象內(nèi)部是直接訪問(wèn)還是使用屬性訪問(wèn)好呢狞山。
例如
@interface Person:NSObject
@property (nonatomic,copy) NSString *firstName;
@property (nonatomic,copy) NSString *lastName;
- (NSString *) fullName;
@end
fullName方法在內(nèi)部有兩種實(shí)現(xiàn)方式
//方法一:使用屬性訪問(wèn)
- (NSString *)fullName
{
return [NSString stringWithFormat:@"%@ %@",self.firstName,self.lastName];
}
//方法二:使用直接訪問(wèn)
- (NSString *)fullName
{
return [NSString stringWithFormat:@"%@ %@",_firstName,_lastName];
}
這兩個(gè)寫(xiě)法的區(qū)別在于
- 直接訪問(wèn)不經(jīng)過(guò)Objective-C的"方法派發(fā)"全闷,所以速度更快一些
- 直接訪問(wèn)實(shí)例變量,不會(huì)調(diào)用其"設(shè)置方法"萍启,這就繞過(guò)了相關(guān)屬性所定義的"內(nèi)存管理語(yǔ)義",比方說(shuō)总珠,如果在ARC下直接訪問(wèn)一個(gè)聲明為copy的屬性,那么并不會(huì)拷貝該屬性勘纯,只會(huì)保留新值釋放舊值局服。
- 如果直接訪問(wèn)實(shí)例變量,不會(huì)觸發(fā)KVO
- 通過(guò)屬性來(lái)訪問(wèn)有助有排查與之相關(guān)的錯(cuò)誤驳遵,因?yàn)榭梢越ogetter和setter設(shè)置斷點(diǎn)
可以看出淫奔,屬性訪問(wèn)和直接訪問(wèn)各有優(yōu)勢(shì),一種折中的方式是:
- 在寫(xiě)入實(shí)例變量的時(shí)候,使用屬性訪問(wèn)
- 在讀取實(shí)例變量的時(shí)候堤结,直接訪問(wèn)唆迁。
這樣既能提高讀取操作的速度,又能控制對(duì)屬性的寫(xiě)入操作竞穷。
需要注意的是唐责,
- 在初始化中應(yīng)該如何設(shè)置屬性值,這種情況下瘾带,一般使用直接訪問(wèn)實(shí)例變量來(lái)讀寫(xiě)變量鼠哥,因?yàn)樽宇惪赡軙?huì)overwrite屬性的設(shè)置方法。
- 懶加載(lazy initialzation), 在懶加載的情況下朴恳,應(yīng)該使用"獲取方法"來(lái)訪問(wèn)屬性抄罕。否則,實(shí)例變量就永遠(yuǎn)不會(huì)被初始化于颖。
懶加載:對(duì)于有些變量呆贿,如果使用頻率較低,而且創(chuàng)建該變量的成本較高恍飘,那么應(yīng)該使用懶加載榨崩,在內(nèi)部其他地方使用懶加載的變量的時(shí)候谴垫,不能使用直接訪問(wèn)的方式來(lái)訪問(wèn)章母,而是要通過(guò)屬性(獲取方法getter)來(lái)訪問(wèn)(注意,只有g(shù)etter內(nèi)部可以直接訪問(wèn)變量)翩剪,例如乳怎,如果Person對(duì)象有個(gè)brain屬性,懶加載的getter可以這樣寫(xiě)
- (Brain *)brain
{
if(!_brain){
_brain = [Brain new];
}
return _brain;
}