第六條:理解“屬性”這一概念
“屬性”(property)是Objective-C的一項(xiàng)特性,用于封裝對(duì)象中的數(shù)據(jù)。
Objective-C對(duì)象通常會(huì)把其所需的數(shù)據(jù)保存為各種實(shí)例變量疟羹。實(shí)例變量一般通過(guò)“存取方法”(accessmethod)來(lái)訪(fǎng)問(wèn)搁料。
其中,“獲取方法”(getter)用于讀變量值萌庆。
而,“設(shè)置方法”(setter)用于寫(xiě)入變量值。
我們可以在類(lèi)接口的public區(qū)段聲明一些實(shí)例變量:
例:
@inteface person:NSObject {
@public
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
我們?cè)贠bjective-C中很少這么做肮疗。
這種寫(xiě)法的問(wèn)題是:對(duì)象布局在編譯期(compile time)就已經(jīng)固定了。只要碰到訪(fǎng)問(wèn)_firstName變量的代碼扒接,編譯器就把其替換為“偏移量”(offset)伪货,這個(gè)偏移量是“硬編碼”(hardcode),表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)钾怔。
這樣做目前看來(lái)沒(méi)有什么問(wèn)題碱呼,但是如果又加了一個(gè)實(shí)例變量,那就麻煩了宗侦。
如果在firstName前加一個(gè)變量dateOfBirth愚臀,那么原來(lái)指向_firstName的地址偏移量就指向了_dateOfBirth了,就會(huì)讀取到錯(cuò)誤的值矾利。
所以姑裂,如果代碼使用了編譯期計(jì)算出來(lái)的偏移量,那么在修改類(lèi)定義之后必須重新編譯梦皮,否則就會(huì)出錯(cuò)炭分。
例如:某個(gè)代碼庫(kù)中的代碼使用了一份舊的類(lèi)定義。如果和其相鏈接的代碼使用了新的類(lèi)定義剑肯,那么運(yùn)行時(shí)就會(huì)出現(xiàn)不兼容現(xiàn)象捧毛。
Objective-C的做法是,把實(shí)例變量當(dāng)做一種存儲(chǔ)偏移量所用的“特殊變量”(special variable),交由“類(lèi)對(duì)象”(class object)保管呀忧。偏移量會(huì)在運(yùn)行期查找师痕,如果類(lèi)定義變了,那么存儲(chǔ)的偏移量也就變了而账,這樣的話(huà)胰坟,無(wú)論何時(shí)訪(fǎng)問(wèn)實(shí)例變量,總能使用正確的偏移量泞辐。甚至可以在運(yùn)行期向類(lèi)中新增實(shí)例變量笔横,這就是穩(wěn)固的“應(yīng)用程序二進(jìn)制接口”(Application Binary Interface,ABI)咐吼。
屬性還有很多的優(yōu)勢(shì):如果使用了屬性的話(huà)吹缔,那么編譯器就會(huì)自動(dòng)編寫(xiě)訪(fǎng)問(wèn)這些屬性所需的方法,此過(guò)程叫做“自動(dòng)合成”(autosynthesis)锯茄。需要強(qiáng)調(diào)的是厢塘,這個(gè)過(guò)程由編譯器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼肌幽。除了生成方法代碼之外晚碾,編譯器還要自動(dòng)向類(lèi)中添加適當(dāng)類(lèi)型的實(shí)例變量,并且在屬性名前面加下劃線(xiàn)喂急,以此作為實(shí)例變量的名字格嘁。
如果你不想讓編譯器自動(dòng)合成存取方法,則可以自己實(shí)現(xiàn)煮岁。如果你只實(shí)現(xiàn)了其中一個(gè)存取方法讥蔽,那么另一個(gè)還是會(huì)由編譯器來(lái)合成。還有一種辦法能阻止編譯器自動(dòng)合成存取方法画机,就是使用@dynamic關(guān)鍵字冶伞,它會(huì)告訴編譯器:不要自動(dòng)創(chuàng)建實(shí)現(xiàn)屬性所用的實(shí)例變量,也不要為其創(chuàng)建存取方法步氏。而且响禽,在編譯訪(fǎng)問(wèn)屬性的代碼時(shí),即使編譯器發(fā)現(xiàn)沒(méi)有定義存取方法荚醒,也不會(huì)報(bào)錯(cuò)芋类,他相信這些方法能在運(yùn)行期找到。
因?yàn)橛行傩圆⒉皇菍?shí)例變量界阁,其數(shù)據(jù)來(lái)自后端數(shù)據(jù)庫(kù)中侯繁。
屬性的特質(zhì)
屬性可以擁有的特質(zhì)分為四類(lèi):
原子性
在默認(rèn)情況下,由編譯器合成的方法會(huì)通過(guò)鎖定機(jī)制確保其原子性泡躯。
如果屬性使用nonatomic特質(zhì)贮竟,則不適用同步鎖丽焊。
讀寫(xiě)權(quán)限
具備“readwrite”特質(zhì)的的屬性擁有“獲取方法”和“設(shè)置方法”
具備“readonly”特質(zhì)的屬性擁有“獲取方法”
內(nèi)存管理語(yǔ)義
屬性用于封裝數(shù)據(jù),而數(shù)據(jù)要有“具體的所有權(quán)語(yǔ)義”
assign:“設(shè)置方法”只會(huì)執(zhí)行對(duì)“純量類(lèi)型”(scalar type咕别,例如CGFloat或NSInterger)的簡(jiǎn)單賦值操作
strong:此特質(zhì)表明了屬性定義了一種“擁有關(guān)系”技健。為這種屬性設(shè)置新值時(shí),會(huì)先保留新值惰拱,并釋放舊值雌贱,然后再將新值設(shè)置上去。
weak:此特質(zhì)表明了屬性定義了一種“非擁有關(guān)系”偿短。為這種屬性設(shè)置新值時(shí)欣孤,設(shè)置方法既不保留新值,也不釋放舊值翔冀。此特質(zhì)類(lèi)似assign导街,然而在屬性所指的對(duì)象被銷(xiāo)毀時(shí),屬性值也會(huì)清空纤子。
copy:此特質(zhì)所表達(dá)的所屬關(guān)系與strong類(lèi)似,然而設(shè)置方法并不保留新值款票,而是將其“copy”控硼。當(dāng)屬性類(lèi)型為NSString *時(shí),經(jīng)常用此特質(zhì)來(lái)保護(hù)其封裝性艾少,保護(hù)數(shù)據(jù)不會(huì)在對(duì)象不知情的情況下被修改卡乾。
unsafe_unretained:此特質(zhì)的語(yǔ)義和assign相同,但是它適用于“對(duì)象類(lèi)型”缚够,該特質(zhì)表達(dá)一種“非擁有關(guān)系”幔妨,當(dāng)目標(biāo)對(duì)象被銷(xiāo)毀時(shí),屬性值不會(huì)自動(dòng)清空(“不安全”谍椅,unsafe)误堡,這一點(diǎn)與weak不同。
atomic與nonatomic的區(qū)別是什么呢雏吭?
具備atomic特質(zhì)的獲取方法會(huì)通過(guò)鎖定機(jī)制來(lái)確保其操作的原子性锁施,這在多線(xiàn)程中,總會(huì)使用到有效的屬性值杖们。
但是悉抵,在iOS開(kāi)發(fā)中,摘完,你會(huì)發(fā)現(xiàn)姥饰,其中所有的屬性都聲明為nonatomic。這樣做的歷史原因是:在iOS中使用同步鎖的開(kāi)銷(xiāo)較大孝治,這回帶來(lái)性能問(wèn)題列粪。一般情況下并不要求屬性必須是“原子的”审磁,因?yàn)檫@并不能保證“線(xiàn)程安全”(thread safe),若要實(shí)現(xiàn)線(xiàn)程安全的操作還要更深層次的鎖機(jī)制才行篱竭。
例如:一個(gè)線(xiàn)程在連續(xù)多次讀取某屬性值的過(guò)程中有別的線(xiàn)程在同時(shí)改寫(xiě)該值力图,那么即便將屬性聲明為atomic,也還是會(huì)讀到不同的屬性值掺逼,因此在iOS開(kāi)發(fā)中吃媒,一般都會(huì)使用nonatomic特質(zhì)。
要點(diǎn):
1.可以用@property語(yǔ)法來(lái)定義對(duì)象中所封裝的數(shù)據(jù)
2.通過(guò)“特質(zhì)”來(lái)指定存儲(chǔ)數(shù)據(jù)所需的正確語(yǔ)義
3.在設(shè)置屬性所對(duì)應(yīng)的實(shí)例變量時(shí)吕喘,一定要遵從該屬性值所聲明的語(yǔ)義
4.開(kāi)發(fā)iOS程序時(shí)應(yīng)該使用nonatomic屬性赘那,因?yàn)閍tomic會(huì)嚴(yán)重影響性能