泛型
泛型版本的函數(shù)使用占位符類型名(這里叫做?T?)贞间,而不是?實(shí)際類型名(例如?Int茉帅、String?或?Double)瘫絮,占位符類型名并不關(guān)心?T?具體的類型齐佳,但它要求?a?和b?必須是相同的類型私恬,T?的實(shí)際類型由每次調(diào)用?swapTwoValues(_:_:)?來決定。
泛型函數(shù)和非泛型函數(shù)的另外一個(gè)不同之處在于這個(gè)泛型函數(shù)名(swapTwoValues(_:_:))后面跟著占位類型名(T)炼吴,并用尖括號(hào)括起來(<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)體和枚舉類型是值類型闷煤,不是引用類型,也不是通過引用的方式存儲(chǔ)和傳遞
自動(dòng)引用計(jì)數(shù)的工作機(jī)制 :當(dāng)你每次創(chuàng)建一個(gè)類的新的實(shí)例的時(shí)候涮瞻,ARC 會(huì)分配一塊內(nè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í)例的屬性和方法將不能再被訪問和調(diào)用絮重。實(shí)際上冤寿,如果你試圖訪問這個(gè)實(shí)例,你的應(yīng)用程序很可能會(huì)崩潰青伤。
為了確保使用中的實(shí)例不會(huì)被銷毀督怜,ARC 會(huì)跟蹤和計(jì)算每一個(gè)實(shí)例正在被多少屬性,常量和變量所引用狠角。哪怕實(shí)例的引用數(shù)為 1号杠,ARC 都不會(huì)銷毀這個(gè)實(shí)例。
為了使上述成為可能丰歌,無論你將實(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 提供了兩種辦法用來解決你在使用類的屬性時(shí)所遇到的循環(huán)強(qiáng)引用問題:弱引用(weak reference)和無主引用(unowned reference)绰筛。
弱引用:弱引用不會(huì)對其引用的實(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ì)被定義為可選類型變量硬猫,而不是常量。
無主引用:和弱引用類似改执,無主引用不會(huì)牢牢保持住引用的實(shí)例啸蜜。和弱引用不同的是,無主引用在其他實(shí)例有相同或者更長的生命周期時(shí)使用辈挂。你可以在聲明屬性或者變量時(shí)衬横,在前面加上關(guān)鍵字?unowned?表示這是一個(gè)無主引用。
無主引用通常都被期望擁有值终蒂。不過 ARC 無法在實(shí)例被銷毀后將無主引用設(shè)為?nil蜂林,因?yàn)榉强蛇x類型的變量不允許被賦值為?nil。
重點(diǎn)
使用無主引用拇泣,你必須確保引用始終指向一個(gè)未銷毀的實(shí)例噪叙。
如果你試圖在實(shí)例被銷毀后,訪問該實(shí)例的無主引用霉翔,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤睁蕾。
無主引用和隱式解包可選值屬性?
上面弱引用和無主引用的例子涵蓋了兩種常用的需要打破循環(huán)強(qiáng)引用的場景。
Person?和?Apartment?的例子展示了兩個(gè)屬性的值都允許為?nil债朵,并會(huì)潛在的產(chǎn)生循環(huán)強(qiáng)引用子眶。這種場景最適合用弱引用來解決。
Customer?和?CreditCard?的例子展示了一個(gè)屬性的值允許為?nil葱弟,而另一個(gè)屬性的值不允許為?nil壹店,這也可能會(huì)產(chǎn)生循環(huán)強(qiáng)引用。這種場景最適合通過無主引用來解決芝加。
然而硅卢,存在著第三種場景,在這種場景中藏杖,兩個(gè)屬性都必須有值将塑,并且初始化完成后永遠(yuǎn)不會(huì)為?nil。在這種場景中蝌麸,需要一個(gè)類使用無主屬性点寥,而另外一個(gè)類使用隱式解包可選值屬性。
閉包的循環(huán)強(qiáng)引用:循環(huán)強(qiáng)引用還會(huì)發(fā)生在當(dāng)你將一個(gè)閉包賦值給類實(shí)例的某個(gè)屬性来吩,并且這個(gè)閉包體中又使用了這個(gè)類實(shí)例時(shí)敢辩。這個(gè)閉包體中可能訪問了實(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ì)上蟆湖,這跟之前的問題是一樣的——兩個(gè)強(qiáng)引用讓彼此一直有效故爵。但是,和兩個(gè)類實(shí)例不同帐姻,這次一個(gè)是類實(shí)例稠集,另一個(gè)是閉包。
Swift 提供了一種優(yōu)雅的方法來解決這個(gè)問題饥瓷,稱之為?閉包捕獲列表(closure capture list)剥纷。
閉包的循環(huán)強(qiáng)引用:循環(huán)強(qiáng)引用還會(huì)發(fā)生在當(dāng)你將一個(gè)閉包賦值給類實(shí)例的某個(gè)屬性,并且這個(gè)閉包體中又使用了這個(gè)類實(shí)例時(shí)呢铆。這個(gè)閉包體中可能訪問了實(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í)定義捕獲列表作為閉包的一部分确买,通過這種方式可以解決閉包和類實(shí)例之間的循環(huán)強(qiáng)引用。捕獲列表定義了閉包體內(nèi)捕獲一個(gè)或者多個(gè)引用類型的規(guī)則纱皆。跟解決兩個(gè)類實(shí)例間的循環(huán)強(qiáng)引用一樣湾趾,聲明每個(gè)捕獲的引用為弱引用或無主引用,而不是強(qiáng)引用派草。應(yīng)當(dāng)根據(jù)代碼關(guān)系來決定使用弱引用還是無主引用搀缠。
注意
Swift 有如下要求:只要在閉包內(nèi)使用?self?的成員,就要用?self.someProperty?或者?self.someMethod()(而不只是?someProperty?或?someMethod())近迁。這提醒你可能會(huì)一不小心就捕獲了?self艺普。
定義捕獲列表:如果閉包有參數(shù)列表和返回類型,把捕獲列表放在它們前面:
如果閉包沒有指明參數(shù)列表或者返回類型,它們會(huì)通過上下文推斷歧譬,那么可以把捕獲列表和關(guān)鍵字?in放在閉包最開始的地方:
弱引用和無主引用:在閉包和捕獲的實(shí)例總是互相引用并且總是同時(shí)銷毀時(shí)岸浑,將閉包內(nèi)的捕獲定義為?無主引用。
相反的瑰步,在被捕獲的引用可能會(huì)變?yōu)?nil?時(shí)助琐,將閉包內(nèi)的捕獲定義為?弱引用。弱引用總是可選類型面氓,并且當(dāng)引用的實(shí)例被銷毀后,弱引用的值會(huì)自動(dòng)置為?nil蛆橡。這使我們可以在閉包體內(nèi)檢查它們是否存在舌界。
注意
如果被捕獲的引用絕對不會(huì)變?yōu)?nil,應(yīng)該用無主引用泰演,而不是弱引用呻拌。
內(nèi)存安全
默認(rèn)情況下,Swift 會(huì)阻止你代碼里不安全的行為睦焕。例如藐握,Swift 會(huì)保證變量在使用之前就完成初始化,在內(nèi)存被回收之后就無法被訪問垃喊,并且數(shù)組的索引會(huì)做越界檢查猾普。
理解內(nèi)存訪問沖突
內(nèi)存訪問的沖突會(huì)發(fā)生在你的代碼嘗試同時(shí)訪問同一個(gè)存儲(chǔ)地址的時(shí)侯。同一個(gè)存儲(chǔ)地址的多個(gè)訪問同時(shí)發(fā)生會(huì)造成不可預(yù)計(jì)或不一致的行為本谜。在 Swift 里初家,有很多修改值的行為都會(huì)持續(xù)好幾行代碼,在修改值的過程中進(jìn)行訪問是有可能發(fā)生的乌助。
內(nèi)存訪問性質(zhì)?
內(nèi)存訪問沖突時(shí)溜在,要考慮內(nèi)存訪問上下文中的這三個(gè)性質(zhì):訪問是讀還是寫,訪問的時(shí)長他托,以及被訪問的存儲(chǔ)地址掖肋。特別是,沖突會(huì)發(fā)生在當(dāng)你有兩個(gè)訪問符合下列的情況:
1.至少有一個(gè)是寫訪問
2.它們訪問的是同一個(gè)存儲(chǔ)地址
3.它們的訪問在時(shí)間線上部分重疊
正常來說赏参,兩個(gè)瞬時(shí)訪問是不可能同時(shí)發(fā)生的志笼。大多數(shù)內(nèi)存訪問都是瞬時(shí)的。然而登刺,有幾種被稱為長期訪問的內(nèi)存訪問方式籽腕,會(huì)在別的代碼執(zhí)行時(shí)持續(xù)進(jìn)行。瞬時(shí)訪問和長期訪問的區(qū)別在于別的代碼有沒有可能在訪問期間同時(shí)訪問纸俭,也就是在時(shí)間線上的重疊皇耗。一個(gè)長期訪問可以被別的長期訪問或瞬時(shí)訪問重疊。
重疊的訪問主要出現(xiàn)在使用 in-out 參數(shù)的函數(shù)和方法或者結(jié)構(gòu)體的 mutating 方法里揍很。Swift 代碼里典型的長期訪問會(huì)在后面進(jìn)行討論郎楼。
In-Out 參數(shù)的訪問沖突:一個(gè)函數(shù)會(huì)對它所有的 in-out 參數(shù)進(jìn)行長期寫訪問万伤。in-out 參數(shù)的寫訪問會(huì)在所有非 in-out 參數(shù)處理完之后開始,直到函數(shù)執(zhí)行完畢為止呜袁。如果有多個(gè) in-out 參數(shù)敌买,則寫訪問開始的順序與參數(shù)的順序一致。
解決這個(gè)沖突的一種方式阶界,是顯示拷貝一份?
屬性的訪問沖突
限制結(jié)構(gòu)體屬性的重疊訪問對于保證內(nèi)存安全不是必要的虹钮。保證內(nèi)存安全是必要的,但因?yàn)樵L問獨(dú)占權(quán)的要求比內(nèi)存安全還要更嚴(yán)格——意味著即使有些代碼違反了訪問獨(dú)占權(quán)的原則膘融,也是內(nèi)存安全的芙粱,所以如果編譯器可以保證這種非專屬的訪問是安全的,那 Swift 就會(huì)允許這種行為的代碼運(yùn)行氧映。特別是當(dāng)你遵循下面的原則時(shí)春畔,它可以保證結(jié)構(gòu)體屬性的重疊訪問是安全的:
你訪問的是實(shí)例的存儲(chǔ)屬性,而不是計(jì)算屬性或類的屬性
結(jié)構(gòu)體是本地變量的值岛都,而非全局變量
結(jié)構(gòu)體要么沒有被閉包捕獲律姨,要么只被非逃逸閉包捕獲了