iOS屬性關(guān)鍵字
引言
學(xué)習(xí) iOS 開發(fā)的人棺滞,大多都繞不開屬性關(guān)鍵字—— assign纷铣,weak将饺,unsafe_unretained贡避,strong,retain 予弧,copy刮吧,readonly,readwrite , nonatomic掖蛤,natomic及 __weak杀捻,__block ,@synthesize 和 @dynamic 等蚓庭;大概很多人都對這個致讥,只要最后程序不崩潰,能正常運行彪置,不會過多去探究拄踪;但實際上,如果可以正確的使用這些屬性拳魁,是可以大大提高代碼質(zhì)量惶桐,同時對代碼結(jié)構(gòu)的理解上也能更上一層樓。
原理
首先要知道倆點潘懊,iOS 代碼實現(xiàn)并不僅僅是靠上層 OC 實現(xiàn)的姚糊,實現(xiàn)的基礎(chǔ)是來源于 apple 的 SDK ,基于這個 SDK 提供的基礎(chǔ)庫授舟,我們才能開發(fā)出 apple 風(fēng)格的 APP救恨;還有一點就是 xcode 代碼提示 、 錯誤释树、警告 等提示 肠槽,其本身就是一種協(xié)議約定擎淤。
這些關(guān)鍵字是怎么來的呢?如果你能明白這個問題秸仙,你就明白了其原理嘴拢。
對一個屬性來說,無非倆個操作寂纪,讀和取席吴,對應(yīng)的就是 get 和 set 方法;通俗一點講捞蛋,這些關(guān)鍵字是底層約定的一些標(biāo)簽孝冒,當(dāng)你上層對聲明的屬性加上這些關(guān)鍵字時,底層會根據(jù)不同的標(biāo)簽拟杉,在 get 和 set 方法中庄涡,執(zhí)行不同的代碼。
舉個例子:
聲明: ` @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 生成的方法中啼染,已經(jīng)包含了這幾個方法 , 這里只是展示一下聲明后得到的方法 焕梅,另外想一想為什么加了這些屬性后代碼提示中就能出這些方法迹鹅。nonatomic、strong 其實就是標(biāo)簽 贞言,根據(jù)這些標(biāo)簽斜棚,生成不同的 set 和 get 執(zhí)行策略。下面就來具體說一說该窗,不同關(guān)鍵字對應(yīng)的策略是什么弟蚀。
nonatomic、atomiac
簡單從詞意上理解酗失,nonatomic 非原子的义钉, atomiac 原子的 。屬性默認(rèn)是 atomiac 规肴, 也就是原子性的捶闸。
這對 CP,是用于區(qū)分在多線程下拖刃,屬性讀取策略删壮。
atomiac: 不受其他線程的影響,在 get 一個屬性時兑牡,立馬給這個屬性在當(dāng)前線程加一個鎖央碟,只有當(dāng) get 完成后,才會解鎖均函,才會同步其他線程的 set 值亿虽。
nonatomic: 受線程影響菱涤,在 get 一個屬性時, 不管是否有其他線程執(zhí)行 set 方法经柴, 只返回 get 結(jié)束時的 set 值狸窘。
從這也可以看出,nonatomic 聲明的屬性坯认,執(zhí)行速率上是要更快一點的 ; 其實 atomiac 這個屬性在上層代碼中,其實非常不常用氓涣,因為很少會遇到存在同時牛哺,多個線程對一個屬性 set 。
readwrite劳吠、readonly
詞意上理解引润,readwrite 讀寫,readonly 只讀痒玩。 屬性默認(rèn)是 readwrite 淳附, 支持讀寫。
這對 CP蠢古,是對set 和 get 方法的一個總開關(guān)奴曙。
readwirte: 屬性同時具有 set 和 get 方法。
readonly: 屬性只具有 get 方法草讶。
這倆關(guān)鍵字洽糟,就是和其詞意一樣 ,若只想類內(nèi)部 set , 就聲明 readonly堕战。
strong坤溃、retain、weak嘱丢、assign薪介、copy、unsafe_unretained
這個幾個從詞意上就很難理解了越驻。 retain 汁政、assign 是 MRC 時的關(guān)鍵字,到 ARC 時伐谈,換成了 strong 和 weak 烂完。 屬性默認(rèn)是 MRC -- assign ;ARC -- object 是 strong,基本數(shù)據(jù)類型還是 assign 诵棵。 實際上 weak 和 assign 還是有一些不同的抠蚣,strong 和 retain 幾乎沒什么區(qū)別,不過建議還是能用 retain 的地方盡量用 strong 履澳, 后面也不講 retain 嘶窄。 講到這幾個關(guān)鍵字怀跛,就必須說到引用計數(shù)(retainCount)和生命周期。
由于 strong 是不能修飾基礎(chǔ)數(shù)據(jù)類型的柄冲,我以為 ARC 中應(yīng)該默認(rèn)屬性關(guān)鍵字應(yīng)該不是 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**.
后面求證后现横,發(fā)現(xiàn)確實也是這樣漓拾,所以猜測 ARC 中 對象默認(rèn)是 strong,基本數(shù)據(jù)類型默認(rèn)還是 assign戒祠。
對整個 APP 來說是內(nèi)存管理機(jī)制骇两,對單個屬性來說就是生命周期 ,而引用計數(shù)就是核心姜盈。
這里簡單說明一下: iOS 有個內(nèi)存池的概念低千,所有的屬性創(chuàng)建的時候都會被內(nèi)存池關(guān)注,這個時候 retainCount = 1 馏颂,中間對這個屬性進(jìn)行操作時示血, retainCount 可能會增加 或者 減少 ,但當(dāng) retainCount = 0 時救拉, 內(nèi)存池檢測到后难审,就會釋放這個屬性對應(yīng)的內(nèi)存空間 。
strong近上、weak剔宪、assign、unsafe_unretained
先從字面上理解一下壹无, strong 強(qiáng)的葱绒,weak 弱的、虛的斗锭, assign 分配 地淀,unsafe_unretained 不安全且不 retain 的。
- strong 和 weak
strong 是每對這個屬性引用一次岖是,retainCount 就會+1帮毁,只能修飾 NSObject 對象,不能修飾基本數(shù)據(jù)類型豺撑。是 id 和 對象 的默認(rèn)修飾符烈疚。
weak 對屬性引用時,retainCount 不變聪轿,只能修飾 NSObject 對象爷肝,不能修飾基本數(shù)據(jù)類型。 主要用于避免循環(huán)引用。
- assign
這個關(guān)鍵字灯抛,是默認(rèn)關(guān)鍵字金赦,可以修飾基本數(shù)據(jù)類型和 NSObject 對象。
對這個關(guān)鍵字聲明的屬性操作時对嚼,retainCount 是一直不變的夹抗,一直為 1,只有主動調(diào)用 release 時 纵竖,才會釋放漠烧。
但是為什么我們不會用assign去聲明對象呢?
這是因為 assign 修飾的對象(一般編譯的時候會產(chǎn)生警告:Assigning retained object to unsafe property; object will be released after assignment)在釋放之后磨确,指針的地址還是存在的沽甥,也就是說指針并沒有被置為nil,造成野指針乏奥。對象分配在堆上的某塊內(nèi)存,如果在后續(xù)的內(nèi)存分配中亥曹,剛好分到了這塊地址邓了,程序就會 crash。
為什么可以用assign修飾基本數(shù)據(jù)類型媳瞪?
因為基礎(chǔ)數(shù)據(jù)類型是分配在棧上骗炉,棧的內(nèi)存會由系統(tǒng)自己自動處理回收,不會造成野指針蛇受。
- unsafe_unretained
這個關(guān)鍵字和 week 非常相似句葵, 也是可以同時修飾基本數(shù)據(jù)類型和 NSObject 對象 ,其實它本身是 week 的前身 , 在 iOS5 之后兢仰,基本都用 week 代替了 unsafe_unretained 乍丈。 但它們之間還是稍微有點區(qū)別的,并不是完全一樣把将,對上層代碼來說轻专,能用 unsafe_unretained 的地方,都可以用 week 代替察蹲。同時要注意一點请垛,這個修飾符修飾的變量不屬于編譯器的內(nèi)存管理對象。
- copy
復(fù)制的意思洽议,意思非常明確宗收,但用起來是最要注意的。
這個關(guān)鍵字類似 strong 亚兄,只能修飾 NSObject 對象混稽,不能修飾基本數(shù)據(jù)類型。和 strong 不一樣的地方是, copy 后的對象 荚坞,指針地址是和之前不一樣的挑宠,也就是說重新分配了一塊內(nèi)存,也就是所謂的深拷貝颓影。這個關(guān)鍵字在用的時候各淀,因為涉及到申請新的內(nèi)存空間,所以要少用诡挂,能用 strong 的地方都用 strong 碎浇,只有必須用 copy 的地方才用 copy 。
另外要注意璃俗,copy 修飾可變類型的屬性時要小心奴璃,如NSMutableArray、NSMutableDictionary城豁、NSMutableString 苟穆,因為會容易造成 crash。
__weak唱星、__block雳旅、__strong、__copy 间聊、__autorelease 等
類似這樣的關(guān)鍵字攒盈,其實和沒有‘__’還是一樣,只是在 .m 中聲明就是這個樣子哎榴,另外提到 .m 文件中型豁,新生產(chǎn)的對象,其實默認(rèn)都有 __strong 尚蝌。
@synthesize 和 @dynamic 分別有什么作用迎变?
- @property 有兩個對應(yīng)的詞,一個是 @synthesize驼壶,一個是 @dynamic氏豌。如果 @synthesize 和 @dynamic 都沒寫,那么默認(rèn)的就是 @syntheszie var = _var;
- @synthesize 的語義是如果你沒有手動實現(xiàn) setter 方法和 getter 方法热凹,那么編譯器會自動為你加上這兩個方法泵喘。
- @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現(xiàn),不自動生成般妙。(當(dāng)然對于 readonly 的屬性只需提供 getter 即可)纪铺。假如一個屬性被聲明為 @dynamic var,然后你沒有提供 @setter 方法和 @getter 方法碟渺,編譯的時候沒問題鲜锚,但是當(dāng)程序運行到 instance.var = someVar,由于缺 setter 方法會導(dǎo)致程序崩潰;或者當(dāng)運行到 someVar = var 時芜繁,由于缺 getter 方法同樣會導(dǎo)致崩潰旺隙。編譯時沒問題,運行時才執(zhí)行相應(yīng)的方法骏令,這就是所謂的動態(tài)綁定蔬捷。
iOS9的幾個新關(guān)鍵字(nonnull、nullable榔袋、null_resettable周拐、__null_unspecified 、__kindof)
- nonnull
字面意思就能知道:不能為空(用來修飾屬性,或者方法的參數(shù),方法的返回值) - nullable
表示可以為空 - null_resettable
get 不能返回空, set 可以為空(注意:如果使用null_resettable,必須重寫 get 方法或者 set 方法,處理傳遞的值為空的情況)) - __null_unspecified
不確定是否為空 (很操蛋凰兑。妥粟。。) - __kindof
放在類型前面,表示修飾這個類型(__kindof MyCustomClass *)
表示當(dāng)前類,也可以表示當(dāng)前類的子類
總結(jié)
對這些關(guān)鍵字的認(rèn)知是非常有必要的吏够,很好的使用這些關(guān)鍵字勾给,可以讓代碼優(yōu)美,另外也減少不必要的開銷锅知,提高APP運行效率锦秒。