@property = ivar + setter + getter
Apple官方文件 這個標題翻譯成中文就是 :
屬性 = 例項變數 + 存取方法
複製程式碼
當我們在一個類中定義屬性(name)時,系統會自動為我們自動生成一個帶下劃線的屬性 (_name) 並新增這個屬性的存取方法 (setter、getter) 。這個過程叫做 autosynthesis (自動合成),在程式碼的編譯期執行。
這就引入了我們接下來要說的 @sythesize 。 但我們還是先說說@property的關鍵字:
-
原子性 在作業系統的學習中我們瞭解到,一個操作具有原子性表明這個操作不可分割,即最基本的操作。這關乎到執行緒是否安全的問題:多個執行緒可能同時訪問一個屬性。如果一個屬性不具備原子性,就不會使用同步鎖。在iOS開發時,我們極大多數情況都用nonatomic非原子性,而在OS X開發時,會使用到atomic原子性。這樣做的原因是在iOS中使用同步鎖,效能開銷較大,在極少數需要加鎖保護執行緒安全的時候,我們還需要更底層的鎖機制。
-
可讀、可寫許可權 這個特質有兩個選項,readwrite和readonly,分別對應讀寫許可權和只讀許可權。只讀許可權編譯器並不會合成“setter”。預設情況,該特質為readwrite。
記憶體管理方式
setter方法中賦值的方式,決定了記憶體管理中引用計數的變化。
這個特質有非常多的選項,我們依次說明。
-
assign:只對簡單資料型別進行賦值操作。簡單資料型別包括NSInteger、CGFloat等。 weak:不對該屬性具備擁有關係。此時的setter方法並不會改變物件的引用計數,如果該物件的擁有者全都release了,那麼該物件會徹底釋放,該weak指標會被置為空nil。
-
strong:對該屬性具備擁有關係。此時的setter方法會retain新的物件,release舊的物件。如果舊的物件之前引用計數為1,那麼在這次重新賦值時就會被徹底釋放。
-
copy:在setter中,會把該物件複製一份並賦給該屬性。我們說複製有兩種,『淺拷貝』和『深拷貝』,『淺拷貝』是指對指標的複製,物件本身的記憶體區域沒有變化,新指標指向的還是原有記憶體區域;『深拷貝』是指對記憶體區域儲存的資料進行的複製,指標會指向新的記憶體區域。特別注意的是,如果是對可變物件的使用copy特質不會改變引用計數,為深拷貝;如果是不可變物件使用copy特質,就是淺拷貝,引用計數++,此時和strong完全相同。通常情況下,我們對可變物件使用copy特質。當型別為NSString時,我們也應該對其加以copy特質,因為setter中的新值可能是一個NSMutableString。
-
unsafe_unretained:不對該屬性具備擁有關係,但和weak的區別在於指標所指向的物件被徹底釋放時,該屬性的指標不會被置為空nil。
-
retain:這是以前在MRC記憶體管理方式下的屬性,在setter方法中對新值retain。ARC中已經棄用! 關於可變物件要使用copy特質:為了保證封裝性,setter方法是屬性的設值方法,但可變物件可能會在不經過setter方法的情況下,改變自身的值,這樣破壞了原有的封裝性。所以要對可變物件做copy操作,深拷貝出一份不可變的物件。
@synthesize 屬性自動合成
在以前 @property 要和 @synthesize 搭配使用,iOS 6 之後編譯器引入了property autosynthesis,即屬性自動合成。編譯器會自動給 @property 新增 @synthesize :
@synthesize propertyName = _propertyName;
複製程式碼
這行程式碼會在編譯期間創造一個帶下劃線的例項變數名,同時使用這個屬性生成getter 和 setter 方法。所以現在,我們的程式碼中已經很少看到 @synthesize 了。 如果你不喜歡這個帶下劃線的名字,你也可以自己來指定喜歡的名字:
@synthesize propertyName = propertyName;
複製程式碼
但帶下劃線的命名方式已經成為開發者們約定俗成的規範,如果沒有特殊要求,還是不要使用@synthesize ,而是直接宣告屬性@property。
當有自定義的 setter 或 getter 方法時 ,會遮蔽自動生成改方法。
注意
setter 或 getter 是不能同時重寫的,否則編譯器不會自動生成該屬性。
@property與KVO
KVO全稱是Key-Value Observing,是Objective-C語言中的一種核心機制,我們一般翻譯為鍵值觀察。至於KVO的實現方式,相信很多人也知道,就是繼承該類並重寫setter方法,當呼叫setter方法的時候,通知監聽器回撥。前文說到了,@property可以自動合成setter,所以這裡正是它和KVO之間千絲萬縷的關係。
我們訪問變數有幾種方式,遵循Objective-C呼叫方法的方式訪問[self variableName]是一種,self.variableName語法糖是一種,直接訪問例項變數_variableName也是一種,這三種方式有什麼區別呢?
方法呼叫[self propertyName]
複製程式碼
如前文所講,這種方式正是使用了getter方法,通過getter返回值的來訪問到例項變數的值,遵循良好的封裝性。
點語法糖self.propertyName
複製程式碼
正如它的名字一樣,這種方式其實只是一種語法糖,其背後根本上還是呼叫getter方法。
直接訪問例項變數_variableName不經過取設方法,直接訪問例項變數所在記憶體位置。優點在於不經過方法呼叫,速度更快。
同樣看一下設值的方式呢,也有如下三種:方法呼叫
[self setPropertyName]
複製程式碼
使用setter來設值。 點語法糖
self.propertyName = @"Name"
複製程式碼
語法糖,背後同樣是setter方法。 直接訪問例項變數
_propertyName = @"Name"
複製程式碼
不經過設值方法,將新值賦給例項變數所在記憶體區域。優點還是不經過方法呼叫,速度更快。然而問題也很明顯,用這種方式改變值KVO失效,跳過了setter中記憶體管理的特質。比如你給某個字串賦了mutable可變字串,若該屬性記憶體管理特質為copy,此時通過setter設值會深拷貝一份不可變字串,但直接訪問該例項變數不通過setter會導致沒有拷貝出一份不可變字串。
對於這樣的問題,我們大概可以總結出最佳使用姿勢:
在需要設值的地方,我們通過setter設值方法設值;當需要取值的時候,我們採用直接訪問例項變數的方式。
另外要注意,初始化方法中,沒有特殊需要,應該儘量直接訪問例項變數。
@dynamic 動態合成
告訴編譯器不需要自動合成屬性的 setter 和 getter 方法,而由開發之自己動態繫結。 需要注意的是,編譯器不會自動生成 屬性 所以還需要手動定義屬性。
更詳盡的資料這裡有