Objective-C的開(kāi)發(fā)者們都知道,OC中的屬性(Property)通常都有一組特性(Attributes)來(lái)說(shuō)明該屬性的一些附加信息。在Swift當(dāng)中,這個(gè)特性的功能“似乎”是被取消掉了,但是,我們?nèi)匀豢梢酝ㄟ^(guò)一些不同的方法來(lái)指明屬性的這些特性。
基本的屬性聲明
使用屬性,我們可以避免手工編寫(xiě)繁瑣的setter和getter方法,避免因?yàn)檫@些方法來(lái)內(nèi)存的問(wèn)題,同時(shí)也節(jié)省編寫(xiě)代碼的時(shí)間。
在Objecitve-C中,我們聲明屬性一般都是這樣聲明的:
// Some.h@propertyintcount;
// Some.m@synthesizecount;
而在Swift當(dāng)中,我們則是這樣聲明就可以了:
// Some.swiftvarcount:Int
注意的是,Swift中的屬性只能夠聲明在類(lèi)的上下文環(huán)境當(dāng)中,而不能聲明在其他地方(包括類(lèi)當(dāng)中的方法),否則Xcode就不會(huì)認(rèn)為它是屬性,而是認(rèn)為它是一個(gè)局部變量了。
讀寫(xiě)特性
特寫(xiě)特性主要是對(duì)屬性的讀寫(xiě)權(quán)限進(jìn)行控制與操作的,這個(gè)特性是針對(duì)于外部的。因?yàn)閷傩詫?shí)際上會(huì)生成兩個(gè)方法:setter和getter,外部訪問(wèn)這個(gè)屬性實(shí)際上是調(diào)用這兩個(gè)方法來(lái)對(duì)屬性進(jìn)行操作的。
在Objective-C中,擁有這樣兩個(gè)讀寫(xiě)特性:readwrite和readonly,意思簡(jiǎn)單明了,就是可讀可寫(xiě)以及只讀。默認(rèn)情況下,屬性默認(rèn)是可讀可寫(xiě)的。
比如說(shuō)我們?cè)贠bjecitve-C中可以聲明這樣一個(gè)只讀屬性:
@property(readonly)intcount;
這樣這個(gè)屬性在外部只能夠讀?。ㄊ褂胓etter),而不能夠修改了,因?yàn)閄code不會(huì)生成這個(gè)屬性對(duì)應(yīng)的setter方法。
而在Swift當(dāng)中,則有如下兩種選擇:
對(duì)于存儲(chǔ)屬性來(lái)說(shuō),只讀特性的屬性應(yīng)該聲明為:
letcount:Int
注意:在Swift1.2之后,常量可以事先不聲明值,而是事后聲明,但是常量的值一經(jīng)確定,那么常量仍然就不可改變。
而對(duì)于計(jì)算屬性來(lái)說(shuō),只讀特性的屬性只需要提供getter方法就可以了。而可讀可寫(xiě)特性的則必須要提供getter和setter方法。例如:
letcount:Int{get{// ...do something}}
setter語(yǔ)意特性
setter語(yǔ)意特性主要是用來(lái)告訴Xcode,對(duì)于這個(gè)屬性,應(yīng)該如何去自動(dòng)實(shí)現(xiàn)它的setter方法。這個(gè)特性主要是針對(duì)非ARC情況的。
在Objective-C中,擁有三個(gè)setter語(yǔ)意特性:assign、retain和copy,默認(rèn)情況下屬性特性是assign的。
assign,簡(jiǎn)單賦值特性,它不會(huì)對(duì)索引計(jì)數(shù)(Reference Counting)進(jìn)行更改。
retain,釋放(release)舊的對(duì)象,然后將舊對(duì)象的值賦予輸入對(duì)象,再將輸入對(duì)象的索引計(jì)數(shù)增加1(retain)。
copy,建立一個(gè)索引計(jì)數(shù)為1的對(duì)象,然后釋放掉舊對(duì)象。
是不是不好理解?沒(méi)關(guān)系,我們舉一個(gè)例子來(lái)研究下。
比如說(shuō)這樣一個(gè)語(yǔ)句聲明:
NSString*paper = [[NSStringalloc] initWithString:@"紙"];
這一段代碼將會(huì)執(zhí)行以下兩種動(dòng)作:
在堆上分配一塊內(nèi)存空間,用來(lái)存儲(chǔ)@"紙"這個(gè)字符串對(duì)象,我們假設(shè)這塊內(nèi)存地址為0x1111`。
在棧上分配一塊內(nèi)存空間,用來(lái)存儲(chǔ)paper這個(gè)對(duì)象,我們假設(shè)這塊內(nèi)存地址為0x2222。
對(duì)于setter語(yǔ)意特性來(lái)說(shuō),它們都是在執(zhí)行setter方法后,對(duì)“舊對(duì)象”執(zhí)行這幾個(gè)操作而已,這三個(gè)操作實(shí)際上也是Objective-C內(nèi)存管理機(jī)制的重要組成部分。
assign
那么對(duì)于assign來(lái)說(shuō),聲明一個(gè)帶有assign特性的屬性,就相當(dāng)于如下語(yǔ)句:
NSString*newsPaper = [paperassign];
此時(shí),paper和newspaper的內(nèi)容都是指向地址0x1111的紙。也就是說(shuō),newspaper只是paper的一個(gè)別名,對(duì)newspaper進(jìn)行變更,也會(huì)對(duì)paper進(jìn)行變更,因?yàn)樗鼈儽举|(zhì)上都是一個(gè)東西。因此,他們的引用計(jì)數(shù)并不會(huì)增加。
換句話說(shuō),assign就是相當(dāng)于是給一個(gè)保險(xiǎn)柜(存放紙的存儲(chǔ)空間)只配了一把鑰匙(指針),無(wú)論是誰(shuí)來(lái)要去打開(kāi)保險(xiǎn)柜,實(shí)際上都是使用了這把鑰匙。
Retain
那么對(duì)于retain來(lái)說(shuō),聲明一個(gè)帶有retain特性的屬性,就相當(dāng)于如下語(yǔ)句:
NSString*newsPaper = [paper retain];
這個(gè)時(shí)候,newsPaper的地址就不是0x2222了,而是變成了一個(gè)新的地址,只不過(guò)它的內(nèi)容仍然還是位于地址0x1111的紙而已,而此時(shí)引用計(jì)數(shù)就會(huì)增加1。
換句話說(shuō),retain就是相當(dāng)于給保險(xiǎn)柜配了多把鑰匙,這些鑰匙都能夠打開(kāi)這個(gè)保險(xiǎn)柜,每多配一把鑰匙,那么引用計(jì)數(shù)(保險(xiǎn)柜所擁有的鑰匙數(shù)量)就要增加1。
Copy
那么對(duì)于copy來(lái)說(shuō),聲明一個(gè)帶有copy特性的屬性,就相當(dāng)于如下語(yǔ)句:
NSString*newsPaper = [papercopy];
這個(gè)時(shí)候,就會(huì)在堆上重新開(kāi)辟一段內(nèi)存空間,來(lái)存放紙這個(gè)對(duì)象,同時(shí)也會(huì)為newsPaper也分配一段新的內(nèi)存空間。這個(gè)時(shí)候,newspaper的地址就是新的了,而它里面的內(nèi)容也會(huì)是新的了。
換句話說(shuō),copy就是相當(dāng)于重新搞了個(gè)保險(xiǎn)柜,可能保險(xiǎn)柜里面的東西都是一樣的,但是鑰匙卻變成兩把了。不過(guò)每個(gè)保險(xiǎn)柜所對(duì)應(yīng)的鑰匙數(shù)量仍然都是為1。
什么時(shí)候使用這些語(yǔ)意特性呢?
只要是值類(lèi)型、簡(jiǎn)單類(lèi)型的類(lèi)型,比如說(shuō)NSInteger、CGPoint、CGFloat,以及C數(shù)據(jù)類(lèi)型int、float、double等,都應(yīng)該使用assign。
那么對(duì)于含有可深復(fù)制子類(lèi)的對(duì)象,比如說(shuō)NSArray、NSSet、NSDictionary、NSData、NSString等等,都應(yīng)該使用copy特性。
注意:對(duì)于NSMutableArray之類(lèi)的可變類(lèi)型,不能夠使用Copy特性,否則初始化會(huì)出現(xiàn)錯(cuò)誤。
至于其他的NSObject對(duì)象,那么都應(yīng)該使用retain來(lái)進(jìn)行操作,這也是絕大多數(shù)所使用的情況。
等等,Swift呢?
我們開(kāi)頭已經(jīng)提到過(guò),getter語(yǔ)意特性是針對(duì)非ARC情況的,我們都知道,Swift語(yǔ)言是直接采用ARC進(jìn)行內(nèi)存管理的,所以這些操作在Swift中都是找不到對(duì)應(yīng)的情況的。
不過(guò),對(duì)于Swift來(lái)說(shuō),有一項(xiàng)特性和copy特性是十分相似的。
NSCopying
Swift中用@NSCopying特性來(lái)修飾存儲(chǔ)屬性,這個(gè)特性將使該屬性的setter與屬性值的一個(gè)副本拷貝合成,也就是說(shuō),@NSCopying特性也是將屬性進(jìn)行了復(fù)制,開(kāi)辟了一段新的內(nèi)存空間,從而達(dá)成“兩個(gè)保險(xiǎn)箱”的作用效果。
和copy特性不同的是,這個(gè)特性將會(huì)導(dǎo)致屬性的getter方法是用copyWithZone方法所返回的值,而不是返回屬性本身的值。因此,這個(gè)屬性的類(lèi)型必須要遵循NSCopying協(xié)議。
所有者特性
對(duì)于ARC來(lái)說(shuō),上一節(jié)中所說(shuō)的getter語(yǔ)意特性將被所有者特性所代替。
在Objective-C中,擁有兩個(gè)所有者特性:strong和weak。默認(rèn)情況下屬性特性是strong的。
對(duì)于strong來(lái)說(shuō),它就相當(dāng)于getter語(yǔ)意特性中的retain特性,即這個(gè)特性的屬性將會(huì)成為對(duì)象的持有者。這個(gè)特性稱之為強(qiáng)引用。
對(duì)于weak來(lái)說(shuō),它聲明的屬性不會(huì)擁有這個(gè)對(duì)象的所有權(quán),當(dāng)對(duì)象被廢棄之后,對(duì)象將被自動(dòng)賦值為nil。
那么怎么來(lái)理解strong和weak呢?我們?nèi)匀灰灾暗谋kU(xiǎn)柜的例子:
假設(shè)保險(xiǎn)柜現(xiàn)在是超市外面的寄存處的保險(xiǎn)柜,大家都知道,寄存處在某個(gè)保險(xiǎn)柜不再使用的時(shí)候(對(duì)象被釋放),就會(huì)回收這個(gè)保險(xiǎn)柜(回收內(nèi)存空間),以供下一個(gè)人使用。
我們申請(qǐng)了一個(gè)保險(xiǎn)柜(申請(qǐng)內(nèi)存空間)之后,我們將我們的東西(對(duì)象)存放到保險(xiǎn)柜當(dāng)中,那么如何要保證我們的東西不會(huì)被超市坑掉呢?這就需要保險(xiǎn)柜給我們的開(kāi)門(mén)的鑰匙(強(qiáng)引用),只要這個(gè)鑰匙還在,那么我們的東西就不會(huì)被回收(一定期限內(nèi))。
再假設(shè)為了防止恐怖分子,超市對(duì)這些保險(xiǎn)柜都置放了掃描裝置,只要我們的東西還在保險(xiǎn)柜里面,那么保安就能夠通過(guò)裝置看到我們的東西(弱引用)。而如果我們用鑰匙把里面的東西拿走了,將鑰匙歸還了(銷(xiāo)毀對(duì)象)。那么保安就不能看到我們的東西了。為了節(jié)省電力,這個(gè)保險(xiǎn)柜的掃描裝置就會(huì)進(jìn)入休眠(所有弱引用變?yōu)閚il)。
面對(duì)ARC機(jī)制中,最令人頭疼的就是“循環(huán)強(qiáng)引用”的問(wèn)題,所謂循環(huán)強(qiáng)引用,就是我們申請(qǐng)了兩個(gè)保險(xiǎn)柜,然后分別將另外一個(gè)保險(xiǎn)柜的鑰匙鎖在了保險(xiǎn)柜當(dāng)中。這樣就會(huì)造成什么現(xiàn)象呢?我們完全就無(wú)法歸還鑰匙了,這兩個(gè)保險(xiǎn)柜就無(wú)法再重新使用了。那么使用弱引用,就不會(huì)出現(xiàn)這個(gè)問(wèn)題了。
好了,我們就此打住,關(guān)于循環(huán)強(qiáng)引用的解決方案,不在本文的敘述范圍之中。
那么Swift呢?和Objective-C一樣,Swift同樣也有strong和weak兩種所有者特性,但是,Swift還有另外一種特性:unowned,無(wú)主引用。無(wú)主引用和弱引用的作用基本是一樣的,不過(guò)與弱引用不同的是,無(wú)主引用不能夠?yàn)閚il。
在一般的開(kāi)發(fā)流程中,往往都建議將delegate和IBOutlet設(shè)置為weak特性,因?yàn)檫@兩個(gè)屬性都極有可能會(huì)被其他類(lèi)所擁有,設(shè)置為weak特性可以防止循環(huán)強(qiáng)引用的產(chǎn)生。
原子特性
原子特性,簡(jiǎn)要來(lái)說(shuō),是針對(duì)多線程而設(shè)置的。Objective-C擁有兩種原子特性,分別是atomic和nonatomic。
我們知道,如果使用多線程的話,有時(shí)會(huì)出現(xiàn)兩個(gè)線程互相等待而導(dǎo)致的死鎖現(xiàn)象。使用atomic特性,Objective-C可以防止這種線程互斥的情況發(fā)生,但是會(huì)造成一定的資源消耗。這個(gè)特性是默認(rèn)的。
而如果使用nonatomic,就不會(huì)有這種阻止死鎖的功能,但是如果我們確定不使用多線程的話,那么使用這個(gè)特性可以極大地改善應(yīng)用性能。
相比之下,swift目前還不支持這些特性。如果我們要實(shí)現(xiàn)線程安全,似乎只能使用objc_sync_enter此類(lèi)的方法,來(lái)保證屬性的處理只有一個(gè)線程在進(jìn)行。或者使用屬性觀察器來(lái)完成這些操作。
總結(jié)
我們總共介紹了四種屬性特性,分別是讀寫(xiě)特性、setter語(yǔ)意特性、所有者特性和原子特性。一個(gè)特性中,只能夠有一個(gè)出現(xiàn),不能夠出現(xiàn)多個(gè)讀寫(xiě)特性的情況。此外,setter語(yǔ)意特性和所有者特性也是互斥的,因?yàn)橐坏┦褂昧怂姓咛匦?,就說(shuō)明項(xiàng)目使用了ARC,而ARC是不支持setter語(yǔ)意特性的。
對(duì)于Swift來(lái)說(shuō),我們目前能夠以其他方式實(shí)現(xiàn)的,也就是“讀寫(xiě)特性”和“所有者特性”而已,其他的特性目前是贊不支持的。因此,可以看到,在不久的將來(lái),原子特性可能也會(huì)提供支持。
綜上所述,我們分析和對(duì)比了Objective-C和Swift的屬性特性,可以看出Swift使用了一些特殊的特性來(lái)實(shí)現(xiàn)原有的Objective-C屬性特性的功能,雖然目前還有很多欠缺的地方,但是也不失減輕了開(kāi)發(fā)者的負(fù)擔(dān)。