泛型
泛型版本的函數(shù)使用占位符類型名(這里叫做?T?),而不是?實(shí)際類型名(例如?Int、String?或?Double),占位符類型名并不關(guān)心?T?具體的類型,但它要求?a?和b?必須是相同的類型,T?的實(shí)際類型由每次調(diào)用?swapTwoValues(_:_:)?來(lái)決定。
泛型函數(shù)和非泛型函數(shù)的另外一個(gè)不同之處在于這個(gè)泛型函數(shù)名(swapTwoValues(_:_:))后面跟著占位類型名(T),并用尖括號(hào)括起來(lái)(<T>)。這個(gè)尖括號(hào)告訴 Swift 那個(gè)?T?是?swapTwoValues(_:_:)?函數(shù)定義內(nèi)的一個(gè)占位類型名,因此 Swift 不會(huì)去查找名為?T的實(shí)際類型。
自動(dòng)引用計(jì)數(shù):引用計(jì)數(shù)僅僅應(yīng)用于類的實(shí)例。結(jié)構(gòu)體和枚舉類型是值類型,不是引用類型,也不是通過(guò)引用的方式存儲(chǔ)和傳遞
自動(dòng)引用計(jì)數(shù)的工作機(jī)制 :當(dāng)你每次創(chuàng)建一個(gè)類的新的實(shí)例的時(shí)候,ARC 會(huì)分配一塊內(nèi)存來(lái)儲(chǔ)存該實(shí)例信息。內(nèi)存中會(huì)包含實(shí)例的類型信息,以及這個(gè)實(shí)例所有相關(guān)的存儲(chǔ)型屬性的值。此外,當(dāng)實(shí)例不再被使用時(shí),ARC 釋放實(shí)例所占用的內(nèi)存,并讓釋放的內(nèi)存能挪作他用。這確保了不再被使用的實(shí)例,不會(huì)一直占用內(nèi)存空間。
然而,當(dāng) ARC 收回和釋放了正在被使用中的實(shí)例,該實(shí)例的屬性和方法將不能再被訪問(wèn)和調(diào)用。實(shí)際上,如果你試圖訪問(wèn)這個(gè)實(shí)例,你的應(yīng)用程序很可能會(huì)崩潰。
為了確保使用中的實(shí)例不會(huì)被銷毀,ARC 會(huì)跟蹤和計(jì)算每一個(gè)實(shí)例正在被多少屬性,常量和變量所引用。哪怕實(shí)例的引用數(shù)為 1,ARC 都不會(huì)銷毀這個(gè)實(shí)例。
為了使上述成為可能,無(wú)論你將實(shí)例賦值給屬性、常量或變量,它們都會(huì)創(chuàng)建此實(shí)例的強(qiáng)引用。之所以稱之為“強(qiáng)”引用,是因?yàn)樗鼤?huì)將實(shí)例牢牢地保持住,只要強(qiáng)引用還在,實(shí)例是不允許被銷毀的。
弱引用:當(dāng) ARC 設(shè)置弱引用為?nil?時(shí),屬性觀察不會(huì)被觸發(fā)。
解決實(shí)例之間的循環(huán)強(qiáng)引用:Swift 提供了兩種辦法用來(lái)解決你在使用類的屬性時(shí)所遇到的循環(huán)強(qiáng)引用問(wèn)題:弱引用(weak reference)和無(wú)主引用(unowned reference)。
弱引用:弱引用不會(huì)對(duì)其引用的實(shí)例保持強(qiáng)引用,因而不會(huì)阻止 ARC 銷毀被引用的實(shí)例。這個(gè)特性阻止了引用變?yōu)檠h(huán)強(qiáng)引用。聲明屬性或者變量時(shí),在前面加上?weak?關(guān)鍵字表明這是一個(gè)弱引用。因?yàn)槿跻貌粫?huì)保持所引用的實(shí)例,即使引用存在,實(shí)例也有可能被銷毀。因此,ARC 會(huì)在引用的實(shí)例被銷毀后自動(dòng)將其弱引用賦值為?nil。并且因?yàn)槿跻眯枰谶\(yùn)行時(shí)允許被賦值為?nil,所以它們會(huì)被定義為可選類型變量,而不是常量。
無(wú)主引用:和弱引用類似,無(wú)主引用不會(huì)牢牢保持住引用的實(shí)例。和弱引用不同的是,無(wú)主引用在其他實(shí)例有相同或者更長(zhǎng)的生命周期時(shí)使用。你可以在聲明屬性或者變量時(shí),在前面加上關(guān)鍵字?unowned?表示這是一個(gè)無(wú)主引用。
無(wú)主引用通常都被期望擁有值。不過(guò) ARC 無(wú)法在實(shí)例被銷毀后將無(wú)主引用設(shè)為?nil,因?yàn)榉强蛇x類型的變量不允許被賦值為?nil。
重點(diǎn)
使用無(wú)主引用,你必須確保引用始終指向一個(gè)未銷毀的實(shí)例。
如果你試圖在實(shí)例被銷毀后,訪問(wèn)該實(shí)例的無(wú)主引用,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。
無(wú)主引用和隱式解包可選值屬性?
上面弱引用和無(wú)主引用的例子涵蓋了兩種常用的需要打破循環(huán)強(qiáng)引用的場(chǎng)景。
Person?和?Apartment?的例子展示了兩個(gè)屬性的值都允許為?nil,并會(huì)潛在的產(chǎn)生循環(huán)強(qiáng)引用。這種場(chǎng)景最適合用弱引用來(lái)解決。
Customer?和?CreditCard?的例子展示了一個(gè)屬性的值允許為?nil,而另一個(gè)屬性的值不允許為?nil,這也可能會(huì)產(chǎn)生循環(huán)強(qiáng)引用。這種場(chǎng)景最適合通過(guò)無(wú)主引用來(lái)解決。
然而,存在著第三種場(chǎng)景,在這種場(chǎng)景中,兩個(gè)屬性都必須有值,并且初始化完成后永遠(yuǎn)不會(huì)為?nil。在這種場(chǎng)景中,需要一個(gè)類使用無(wú)主屬性,而另外一個(gè)類使用隱式解包可選值屬性。
閉包的循環(huán)強(qiáng)引用:循環(huán)強(qiáng)引用還會(huì)發(fā)生在當(dāng)你將一個(gè)閉包賦值給類實(shí)例的某個(gè)屬性,并且這個(gè)閉包體中又使用了這個(gè)類實(shí)例時(shí)。這個(gè)閉包體中可能訪問(wèn)了實(shí)例的某個(gè)屬性,例如?self.someProperty,或者閉包中調(diào)用了實(shí)例的某個(gè)方法,例如?self.someMethod()。這兩種情況都導(dǎo)致了閉包“捕獲”self,從而產(chǎn)生了循環(huán)強(qiáng)引用。
循環(huán)強(qiáng)引用的產(chǎn)生,是因?yàn)殚]包和類相似,都是引用類型。當(dāng)你把一個(gè)閉包賦值給某個(gè)屬性時(shí),你是將這個(gè)閉包的引用賦值給了屬性。實(shí)質(zhì)上,這跟之前的問(wèn)題是一樣的——兩個(gè)強(qiáng)引用讓彼此一直有效。但是,和兩個(gè)類實(shí)例不同,這次一個(gè)是類實(shí)例,另一個(gè)是閉包。
Swift 提供了一種優(yōu)雅的方法來(lái)解決這個(gè)問(wèn)題,稱之為?閉包捕獲列表(closure capture list)。
閉包的循環(huán)強(qiáng)引用:循環(huán)強(qiáng)引用還會(huì)發(fā)生在當(dāng)你將一個(gè)閉包賦值給類實(shí)例的某個(gè)屬性,并且這個(gè)閉包體中又使用了這個(gè)類實(shí)例時(shí)。這個(gè)閉包體中可能訪問(wèn)了實(shí)例的某個(gè)屬性,例如?self.someProperty,或者閉包中調(diào)用了實(shí)例的某個(gè)方法,例如?self.someMethod()。這兩種情況都導(dǎo)致了閉包“捕獲”self,從而產(chǎn)生了循環(huán)強(qiáng)引用。
解決閉包的循環(huán)強(qiáng)引用:在定義閉包時(shí)同時(shí)定義捕獲列表作為閉包的一部分,通過(guò)這種方式可以解決閉包和類實(shí)例之間的循環(huán)強(qiáng)引用。捕獲列表定義了閉包體內(nèi)捕獲一個(gè)或者多個(gè)引用類型的規(guī)則。跟解決兩個(gè)類實(shí)例間的循環(huán)強(qiáng)引用一樣,聲明每個(gè)捕獲的引用為弱引用或無(wú)主引用,而不是強(qiáng)引用。應(yīng)當(dāng)根據(jù)代碼關(guān)系來(lái)決定使用弱引用還是無(wú)主引用。
注意
Swift 有如下要求:只要在閉包內(nèi)使用?self?的成員,就要用?self.someProperty?或者?self.someMethod()(而不只是?someProperty?或?someMethod())。這提醒你可能會(huì)一不小心就捕獲了?self。
定義捕獲列表:如果閉包有參數(shù)列表和返回類型,把捕獲列表放在它們前面:
如果閉包沒(méi)有指明參數(shù)列表或者返回類型,它們會(huì)通過(guò)上下文推斷,那么可以把捕獲列表和關(guān)鍵字?in放在閉包最開(kāi)始的地方:
弱引用和無(wú)主引用:在閉包和捕獲的實(shí)例總是互相引用并且總是同時(shí)銷毀時(shí),將閉包內(nèi)的捕獲定義為?無(wú)主引用。
相反的,在被捕獲的引用可能會(huì)變?yōu)?nil?時(shí),將閉包內(nèi)的捕獲定義為?弱引用。弱引用總是可選類型,并且當(dāng)引用的實(shí)例被銷毀后,弱引用的值會(huì)自動(dòng)置為?nil。這使我們可以在閉包體內(nèi)檢查它們是否存在。
注意
如果被捕獲的引用絕對(duì)不會(huì)變?yōu)?nil,應(yīng)該用無(wú)主引用,而不是弱引用。
內(nèi)存安全
默認(rèn)情況下,Swift 會(huì)阻止你代碼里不安全的行為。例如,Swift 會(huì)保證變量在使用之前就完成初始化,在內(nèi)存被回收之后就無(wú)法被訪問(wèn),并且數(shù)組的索引會(huì)做越界檢查。
理解內(nèi)存訪問(wèn)沖突
內(nèi)存訪問(wèn)的沖突會(huì)發(fā)生在你的代碼嘗試同時(shí)訪問(wèn)同一個(gè)存儲(chǔ)地址的時(shí)侯。同一個(gè)存儲(chǔ)地址的多個(gè)訪問(wèn)同時(shí)發(fā)生會(huì)造成不可預(yù)計(jì)或不一致的行為。在 Swift 里,有很多修改值的行為都會(huì)持續(xù)好幾行代碼,在修改值的過(guò)程中進(jìn)行訪問(wèn)是有可能發(fā)生的。
內(nèi)存訪問(wèn)性質(zhì)?
內(nèi)存訪問(wèn)沖突時(shí),要考慮內(nèi)存訪問(wèn)上下文中的這三個(gè)性質(zhì):訪問(wèn)是讀還是寫(xiě),訪問(wèn)的時(shí)長(zhǎng),以及被訪問(wèn)的存儲(chǔ)地址。特別是,沖突會(huì)發(fā)生在當(dāng)你有兩個(gè)訪問(wèn)符合下列的情況:
1.至少有一個(gè)是寫(xiě)訪問(wèn)
2.它們?cè)L問(wèn)的是同一個(gè)存儲(chǔ)地址
3.它們的訪問(wèn)在時(shí)間線上部分重疊
正常來(lái)說(shuō),兩個(gè)瞬時(shí)訪問(wèn)是不可能同時(shí)發(fā)生的。大多數(shù)內(nèi)存訪問(wèn)都是瞬時(shí)的。然而,有幾種被稱為長(zhǎng)期訪問(wèn)的內(nèi)存訪問(wèn)方式,會(huì)在別的代碼執(zhí)行時(shí)持續(xù)進(jìn)行。瞬時(shí)訪問(wèn)和長(zhǎng)期訪問(wèn)的區(qū)別在于別的代碼有沒(méi)有可能在訪問(wèn)期間同時(shí)訪問(wèn),也就是在時(shí)間線上的重疊。一個(gè)長(zhǎng)期訪問(wèn)可以被別的長(zhǎng)期訪問(wèn)或瞬時(shí)訪問(wèn)重疊。
重疊的訪問(wèn)主要出現(xiàn)在使用 in-out 參數(shù)的函數(shù)和方法或者結(jié)構(gòu)體的 mutating 方法里。Swift 代碼里典型的長(zhǎng)期訪問(wèn)會(huì)在后面進(jìn)行討論。
In-Out 參數(shù)的訪問(wèn)沖突:一個(gè)函數(shù)會(huì)對(duì)它所有的 in-out 參數(shù)進(jìn)行長(zhǎng)期寫(xiě)訪問(wèn)。in-out 參數(shù)的寫(xiě)訪問(wèn)會(huì)在所有非 in-out 參數(shù)處理完之后開(kāi)始,直到函數(shù)執(zhí)行完畢為止。如果有多個(gè) in-out 參數(shù),則寫(xiě)訪問(wèn)開(kāi)始的順序與參數(shù)的順序一致。
解決這個(gè)沖突的一種方式,是顯示拷貝一份?
屬性的訪問(wèn)沖突
限制結(jié)構(gòu)體屬性的重疊訪問(wèn)對(duì)于保證內(nèi)存安全不是必要的。保證內(nèi)存安全是必要的,但因?yàn)樵L問(wèn)獨(dú)占權(quán)的要求比內(nèi)存安全還要更嚴(yán)格——意味著即使有些代碼違反了訪問(wèn)獨(dú)占權(quán)的原則,也是內(nèi)存安全的,所以如果編譯器可以保證這種非專屬的訪問(wèn)是安全的,那 Swift 就會(huì)允許這種行為的代碼運(yùn)行。特別是當(dāng)你遵循下面的原則時(shí),它可以保證結(jié)構(gòu)體屬性的重疊訪問(wèn)是安全的:
你訪問(wèn)的是實(shí)例的存儲(chǔ)屬性,而不是計(jì)算屬性或類的屬性
結(jié)構(gòu)體是本地變量的值,而非全局變量
結(jié)構(gòu)體要么沒(méi)有被閉包捕獲,要么只被非逃逸閉包捕獲了