iOS屬性關鍵字
引言
學習 iOS 開發的人,大多都繞不開屬性關鍵字—— assign,weak,unsafe_unretained,strong,retain ,copy,readonly,readwrite , nonatomic,natomic及 __weak,__block ,@synthesize 和 @dynamic 等;大概很多人都對這個,只要最后程序不崩潰,能正常運行,不會過多去探究;但實際上,如果可以正確的使用這些屬性,是可以大大提高代碼質量,同時對代碼結構的理解上也能更上一層樓。
原理
首先要知道倆點,iOS 代碼實現并不僅僅是靠上層 OC 實現的,實現的基礎是來源于 apple 的 SDK ,基于這個 SDK 提供的基礎庫,我們才能開發出 apple 風格的 APP;還有一點就是 xcode 代碼提示 、 錯誤、警告 等提示 ,其本身就是一種協議約定。
這些關鍵字是怎么來的呢?如果你能明白這個問題,你就明白了其原理。
對一個屬性來說,無非倆個操作,讀和取,對應的就是 get 和 set 方法;通俗一點講,這些關鍵字是底層約定的一些標簽,當你上層對聲明的屬性加上這些關鍵字時,底層會根據不同的標簽,在 get 和 set 方法中,執行不同的代碼。
舉個例子:
聲明: ` @property (nonatomic,strong) NSString *name;`
@synthesize name = _name;
// set 方法
- (void)setName:(NSString *)name
{
_name = name;
}
// get 方法
- (NSString *)name
{
return _name;
}
- (void)getName:(NSString **)buffer range:(NSRange)inRange
{
*buffer = _name;
}
這上面的代碼其實是沒有意義的,因為底層 SDK 生成的方法中,已經包含了這幾個方法 , 這里只是展示一下聲明后得到的方法 ,另外想一想為什么加了這些屬性后代碼提示中就能出這些方法。nonatomic、strong 其實就是標簽 ,根據這些標簽,生成不同的 set 和 get 執行策略。下面就來具體說一說,不同關鍵字對應的策略是什么。
nonatomic、atomiac
簡單從詞意上理解,nonatomic 非原子的, atomiac 原子的 。屬性默認是 atomiac , 也就是原子性的。
這對 CP,是用于區分在多線程下,屬性讀取策略。
atomiac: 不受其他線程的影響,在 get 一個屬性時,立馬給這個屬性在當前線程加一個鎖,只有當 get 完成后,才會解鎖,才會同步其他線程的 set 值。
nonatomic: 受線程影響,在 get 一個屬性時, 不管是否有其他線程執行 set 方法, 只返回 get 結束時的 set 值。
從這也可以看出,nonatomic 聲明的屬性,執行速率上是要更快一點的 ; 其實 atomiac 這個屬性在上層代碼中,其實非常不常用,因為很少會遇到存在同時,多個線程對一個屬性 set 。
readwrite、readonly
詞意上理解,readwrite 讀寫,readonly 只讀。 屬性默認是 readwrite , 支持讀寫。
這對 CP,是對set 和 get 方法的一個總開關。
readwirte: 屬性同時具有 set 和 get 方法。
readonly: 屬性只具有 get 方法。
這倆關鍵字,就是和其詞意一樣 ,若只想類內部 set , 就聲明 readonly。
strong、retain、weak、assign、copy、unsafe_unretained
這個幾個從詞意上就很難理解了。 retain 、assign 是 MRC 時的關鍵字,到 ARC 時,換成了 strong 和 weak 。 屬性默認是 MRC -- assign ;ARC -- object 是 strong,基本數據類型還是 assign 。 實際上 weak 和 assign 還是有一些不同的,strong 和 retain 幾乎沒什么區別,不過建議還是能用 retain 的地方盡量用 strong , 后面也不講 retain 。 講到這幾個關鍵字,就必須說到引用計數(retainCount)和生命周期。
由于 strong 是不能修飾基礎數據類型的,我以為 ARC 中應該默認屬性關鍵字應該不是 strong,但實際上不是,下面是 iOS Document 提到
Use Strong and Weak Declarations to Manage Ownership
By default, object properties declared like this:
@property id delegate;
use strong references for their synthesized instance variables. To declare a weak reference, add an attribute to the property, like this:
@property (weak) id delegate;
Note: The opposite to weak is strong. There’s no need to specify the **strong** attribute explicitly, because it is the **default**.
后面求證后,發現確實也是這樣,所以猜測 ARC 中 對象默認是 strong,基本數據類型默認還是 assign。
對整個 APP 來說是內存管理機制,對單個屬性來說就是生命周期 ,而引用計數就是核心。
這里簡單說明一下: iOS 有個內存池的概念,所有的屬性創建的時候都會被內存池關注,這個時候 retainCount = 1 ,中間對這個屬性進行操作時, retainCount 可能會增加 或者 減少 ,但當 retainCount = 0 時, 內存池檢測到后,就會釋放這個屬性對應的內存空間 。
strong、weak、assign、unsafe_unretained
先從字面上理解一下, strong 強的,weak 弱的、虛的, assign 分配 ,unsafe_unretained 不安全且不 retain 的。
- strong 和 weak
strong 是每對這個屬性引用一次,retainCount 就會+1,只能修飾 NSObject 對象,不能修飾基本數據類型。是 id 和 對象 的默認修飾符。
weak 對屬性引用時,retainCount 不變,只能修飾 NSObject 對象,不能修飾基本數據類型。 主要用于避免循環引用。
- assign
這個關鍵字,是默認關鍵字,可以修飾基本數據類型和 NSObject 對象。
對這個關鍵字聲明的屬性操作時,retainCount 是一直不變的,一直為 1,只有主動調用 release 時 ,才會釋放。
但是為什么我們不會用assign去聲明對象呢?
這是因為 assign 修飾的對象(一般編譯的時候會產生警告:Assigning retained object to unsafe property; object will be released after assignment)在釋放之后,指針的地址還是存在的,也就是說指針并沒有被置為nil,造成野指針。對象分配在堆上的某塊內存,如果在后續的內存分配中,剛好分到了這塊地址,程序就會 crash。
為什么可以用assign修飾基本數據類型?
因為基礎數據類型是分配在棧上,棧的內存會由系統自己自動處理回收,不會造成野指針。
- unsafe_unretained
這個關鍵字和 week 非常相似, 也是可以同時修飾基本數據類型和 NSObject 對象 ,其實它本身是 week 的前身 , 在 iOS5 之后,基本都用 week 代替了 unsafe_unretained 。 但它們之間還是稍微有點區別的,并不是完全一樣,對上層代碼來說,能用 unsafe_unretained 的地方,都可以用 week 代替。同時要注意一點,這個修飾符修飾的變量不屬于編譯器的內存管理對象。
- copy
復制的意思,意思非常明確,但用起來是最要注意的。
這個關鍵字類似 strong ,只能修飾 NSObject 對象,不能修飾基本數據類型。和 strong 不一樣的地方是, copy 后的對象 ,指針地址是和之前不一樣的,也就是說重新分配了一塊內存,也就是所謂的深拷貝。這個關鍵字在用的時候,因為涉及到申請新的內存空間,所以要少用,能用 strong 的地方都用 strong ,只有必須用 copy 的地方才用 copy 。
另外要注意,copy 修飾可變類型的屬性時要小心,如NSMutableArray、NSMutableDictionary、NSMutableString ,因為會容易造成 crash。
__weak、__block、__strong、__copy 、__autorelease 等
類似這樣的關鍵字,其實和沒有‘__’還是一樣,只是在 .m 中聲明就是這個樣子,另外提到 .m 文件中,新生產的對象,其實默認都有 __strong 。
@synthesize 和 @dynamic 分別有什么作用?
- @property 有兩個對應的詞,一個是 @synthesize,一個是 @dynamic。如果 @synthesize 和 @dynamic 都沒寫,那么默認的就是 @syntheszie var = _var;
- @synthesize 的語義是如果你沒有手動實現 setter 方法和 getter 方法,那么編譯器會自動為你加上這兩個方法。
- @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現,不自動生成。(當然對于 readonly 的屬性只需提供 getter 即可)。假如一個屬性被聲明為 @dynamic var,然后你沒有提供 @setter 方法和 @getter 方法,編譯的時候沒問題,但是當程序運行到 instance.var = someVar,由于缺 setter 方法會導致程序崩潰;或者當運行到 someVar = var 時,由于缺 getter 方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
iOS9的幾個新關鍵字(nonnull、nullable、null_resettable、__null_unspecified 、__kindof)
- nonnull
字面意思就能知道:不能為空(用來修飾屬性,或者方法的參數,方法的返回值) - nullable
表示可以為空 - null_resettable
get 不能返回空, set 可以為空(注意:如果使用null_resettable,必須重寫 get 方法或者 set 方法,處理傳遞的值為空的情況)) - __null_unspecified
不確定是否為空 (很操蛋。。。) - __kindof
放在類型前面,表示修飾這個類型(__kindof MyCustomClass *)
表示當前類,也可以表示當前類的子類
總結
對這些關鍵字的認知是非常有必要的,很好的使用這些關鍵字,可以讓代碼優美,另外也減少不必要的開銷,提高APP運行效率。