在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 自動(dòng)引用計(jì)數(shù)
方法 - Methods
關(guān)于 Swift
下標(biāo)腳本(Subscripts)
類(lèi)和結(jié)構(gòu)體
類(lèi)型轉(zhuǎn)換(Type Casting)
控制流
析構(gòu)過(guò)程(Deinitialization)
集合類(lèi)型 (Collection Types)
構(gòu)造過(guò)程(Initialization)
Optional Chaining
枚舉(Enumerations)
自動(dòng)引用計(jì)數(shù)
繼承(Inheritance)
擴(kuò)展(Extensions)
泛型
字符串和字符(Strings and Characters)
函數(shù)(Functions)
高級(jí)運(yùn)算符
訪問(wèn)控制
基本運(yùn)算符
嵌套類(lèi)型
閉包(Closures)
協(xié)議
屬性 (Properties)

自動(dòng)引用計(jì)數(shù)

Swift 使用自動(dòng)引用計(jì)數(shù)(ARC)這一機(jī)制來(lái)跟蹤和管理你的應(yīng)用程序的內(nèi)存。通常情況下,Swift 的內(nèi)存管理機(jī)制會(huì)一直起著作用,你無(wú)須自己來(lái)考慮內(nèi)存的管理。ARC 會(huì)在類(lèi)的實(shí)例不再被使用時(shí),自動(dòng)釋放其占用的內(nèi)存。

然而,在少數(shù)情況下,ARC 為了能幫助你管理內(nèi)存,需要更多的關(guān)于你的代碼之間關(guān)系的信息。本章描述了這些情況,并且為你示范怎樣啟用 ARC 來(lái)管理你的應(yīng)用程序的內(nèi)存。

注意:
引用計(jì)數(shù)僅僅應(yīng)用于類(lèi)的實(shí)例。結(jié)構(gòu)體和枚舉類(lèi)型是值類(lèi)型,不是引用類(lèi)型,也不是通過(guò)引用的方式存儲(chǔ)和傳遞。

自動(dòng)引用計(jì)數(shù)的工作機(jī)制

當(dāng)你每次創(chuàng)建一個(gè)類(lèi)的新的實(shí)例的時(shí)候,ARC 會(huì)分配一大塊內(nèi)存用來(lái)儲(chǔ)存實(shí)例的信息。內(nèi)存中會(huì)包含實(shí)例的類(lèi)型信息,以及這個(gè)實(shí)例所有相關(guān)屬性的值。此外,當(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ì)被銷(xiāo)毀,ARC 會(huì)跟蹤和計(jì)算每一個(gè)實(shí)例正在被多少屬性,常量和變量所引用。哪怕實(shí)例的引用數(shù)為一,ARC都不會(huì)銷(xiāo)毀這個(gè)實(shí)例。

為了使之成為可能,無(wú)論你將實(shí)例賦值給屬性,常量或者是變量,屬性,常量或者變量,都會(huì)對(duì)此實(shí)例創(chuàng)建強(qiáng)引用。之所以稱(chēng)之為強(qiáng)引用,是因?yàn)樗鼤?huì)將實(shí)例牢牢的保持住,只要強(qiáng)引用還在,實(shí)例是不允許被銷(xiāo)毀的。

自動(dòng)引用計(jì)數(shù)實(shí)踐

下面的例子展示了自動(dòng)引用計(jì)數(shù)的工作機(jī)制。例子以一個(gè)簡(jiǎn)單的Person類(lèi)開(kāi)始,并定義了一個(gè)叫name的常量屬性:

    class Person {
        let name: String
        init(name: String) {
            self.name = name
            println("\(name) is being initialized")
        }
        deinit {
            println("\(name) is being deinitialized")
        }
    }

Person類(lèi)有一個(gè)構(gòu)造函數(shù),此構(gòu)造函數(shù)為實(shí)例的name屬性賦值并打印出信息,以表明初始化過(guò)程生效。Person類(lèi)同時(shí)也擁有析構(gòu)函數(shù),同樣會(huì)在實(shí)例被銷(xiāo)毀的時(shí)候打印出信息。

接下來(lái)的代碼片段定義了三個(gè)類(lèi)型為Person?的變量,用來(lái)按照代碼片段中的順序,為新的Person實(shí)例建立多個(gè)引用。由于這些變量是被定義為可選類(lèi)型(Person?,而不是Person),它們的值會(huì)被自動(dòng)初始化為nil,目前還不會(huì)引用到Person類(lèi)的實(shí)例。

    var reference1: Person?
    var reference2: Person?
    var reference3: Person?

現(xiàn)在你可以創(chuàng)建Person類(lèi)的新實(shí)例,并且將它賦值給三個(gè)變量其中的一個(gè):

    reference1 = Person(name: "John Appleseed")
    // prints "John Appleseed is being initialized”

應(yīng)當(dāng)注意到當(dāng)你調(diào)用Person類(lèi)的構(gòu)造函數(shù)的時(shí)候,"John Appleseed is being initialized”會(huì)被打印出來(lái)。由此可以確定構(gòu)造函數(shù)被執(zhí)行。

由于Person類(lèi)的新實(shí)例被賦值給了reference1變量,所以reference1Person類(lèi)的新實(shí)例之間建立了一個(gè)強(qiáng)引用。正是因?yàn)檫@個(gè)強(qiáng)引用,ARC 會(huì)保證Person實(shí)例被保持在內(nèi)存中不被銷(xiāo)毀。

如果你將同樣的Person實(shí)例也賦值給其他兩個(gè)變量,該實(shí)例又會(huì)多出兩個(gè)強(qiáng)引用:

    reference2 = reference1
    reference3 = reference1

現(xiàn)在這個(gè)Person實(shí)例已經(jīng)有三個(gè)強(qiáng)引用了。

如果你通過(guò)給兩個(gè)變量賦值nil的方式斷開(kāi)兩個(gè)強(qiáng)引用()包括最先的那個(gè)強(qiáng)引用),只留下一個(gè)強(qiáng)引用,Person實(shí)例不會(huì)被銷(xiāo)毀:

    reference1 = nil
    reference2 = nil

ARC 會(huì)在第三個(gè),也即最后一個(gè)強(qiáng)引用被斷開(kāi)的時(shí)候,銷(xiāo)毀Person實(shí)例,這也意味著你不再使用這個(gè)Person實(shí)例:

    reference3 = nil
    // prints "John Appleseed is being deinitialized"

類(lèi)實(shí)例之間的循環(huán)強(qiáng)引用

在上面的例子中,ARC 會(huì)跟蹤你所新創(chuàng)建的Person實(shí)例的引用數(shù)量,并且會(huì)在Person實(shí)例不再被需要時(shí)銷(xiāo)毀它。

然而,我們可能會(huì)寫(xiě)出這樣的代碼,一個(gè)類(lèi)永遠(yuǎn)不會(huì)有0個(gè)強(qiáng)引用。這種情況發(fā)生在兩個(gè)類(lèi)實(shí)例互相保持對(duì)方的強(qiáng)引用,并讓對(duì)方不被銷(xiāo)毀。這就是所謂的循環(huán)強(qiáng)引用。

你可以通過(guò)定義類(lèi)之間的關(guān)系為弱引用或者無(wú)主引用,以此替代強(qiáng)引用,從而解決循環(huán)強(qiáng)引用的問(wèn)題。具體的過(guò)程在解決類(lèi)實(shí)例之間的循環(huán)強(qiáng)引用中有描述。不管怎樣,在你學(xué)習(xí)怎樣解決循環(huán)強(qiáng)引用之前,很有必要了解一下它是怎樣產(chǎn)生的。

下面展示了一個(gè)不經(jīng)意產(chǎn)生循環(huán)強(qiáng)引用的例子。例子定義了兩個(gè)類(lèi):PersonApartment,用來(lái)建模公寓和它其中的居民:

    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { println("\(name) is being deinitialized") }
    }
    class Apartment {
        let number: Int
        init(number: Int) { self.number = number }
        var tenant: Person?
        deinit { println("Apartment #\(number) is being deinitialized") }
    }

每一個(gè)Person實(shí)例有一個(gè)類(lèi)型為String,名字為name的屬性,并有一個(gè)可選的初始化為nilapartment屬性。apartment屬性是可選的,因?yàn)橐粋€(gè)人并不總是擁有公寓。

類(lèi)似的,每個(gè)Apartment實(shí)例有一個(gè)叫number,類(lèi)型為Int的屬性,并有一個(gè)可選的初始化為niltenant屬性。tenant屬性是可選的,因?yàn)橐粭澒⒉⒉豢偸怯芯用瘛?/p>

這兩個(gè)類(lèi)都定義了析構(gòu)函數(shù),用以在類(lèi)實(shí)例被析構(gòu)的時(shí)候輸出信息。這讓你能夠知曉PersonApartment的實(shí)例是否像預(yù)期的那樣被銷(xiāo)毀。

接下來(lái)的代碼片段定義了兩個(gè)可選類(lèi)型的變量johnnumber73,并分別被設(shè)定為下面的ApartmentPerson的實(shí)例。這兩個(gè)變量都被初始化為nil,并為可選的:

    var john: Person?
    var number73: Apartment?

現(xiàn)在你可以創(chuàng)建特定的PersonApartment實(shí)例并將類(lèi)實(shí)例賦值給johnnumber73變量:

    john = Person(name: "John Appleseed")
    number73 = Apartment(number: 73)

在兩個(gè)實(shí)例被創(chuàng)建和賦值后,下圖表現(xiàn)了強(qiáng)引用的關(guān)系。變量john現(xiàn)在有一個(gè)指向Person實(shí)例的強(qiáng)引用,而變量number73有一個(gè)指向Apartment實(shí)例的強(qiáng)引用:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_1.png" alt="image of Automatic_Reference_Counting_1.png" />

現(xiàn)在你能夠?qū)⑦@兩個(gè)實(shí)例關(guān)聯(lián)在一起,這樣人就能有公寓住了,而公寓也有了房客。注意感嘆號(hào)是用來(lái)展開(kāi)和訪問(wèn)可選變量johnnumber73中的實(shí)例,這樣實(shí)例的屬性才能被賦值:

    john!.apartment = number73
    number73!.tenant = john

在將兩個(gè)實(shí)例聯(lián)系在一起之后,強(qiáng)引用的關(guān)系如圖所示:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_2.png" alt="image of Automatic_Reference_Counting_2.png" />

不幸的是,將這兩個(gè)實(shí)例關(guān)聯(lián)在一起之后,一個(gè)循環(huán)強(qiáng)引用被創(chuàng)建了。Person實(shí)例現(xiàn)在有了一個(gè)指向Apartment實(shí)例的強(qiáng)引用,而Apartment實(shí)例也有了一個(gè)指向Person實(shí)例的強(qiáng)引用。因此,當(dāng)你斷開(kāi)johnnumber73變量所持有的強(qiáng)引用時(shí),引用計(jì)數(shù)并不會(huì)降為 0,實(shí)例也不會(huì)被 ARC 銷(xiāo)毀:

    john = nil
    number73 = nil

注意,當(dāng)你把這兩個(gè)變量設(shè)為nil時(shí),沒(méi)有任何一個(gè)析構(gòu)函數(shù)被調(diào)用。強(qiáng)引用循環(huán)阻止了PersonApartment類(lèi)實(shí)例的銷(xiāo)毀,并在你的應(yīng)用程序中造成了內(nèi)存泄漏。

在你將johnnumber73賦值為nil后,強(qiáng)引用關(guān)系如下圖:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_3.png" alt="image of Automatic_Reference_Counting_3.png" />

PersonApartment實(shí)例之間的強(qiáng)引用關(guān)系保留了下來(lái)并且不會(huì)被斷開(kāi)。

解決實(shí)例之間的循環(huán)強(qiáng)引用

Swift 提供了兩種辦法用來(lái)解決你在使用類(lèi)的屬性時(shí)所遇到的循環(huán)強(qiáng)引用問(wèn)題:弱引用(weak reference)和無(wú)主引用(unowned reference)。

弱引用和無(wú)主引用允許循環(huán)引用中的一個(gè)實(shí)例引用另外一個(gè)實(shí)例而不保持強(qiáng)引用。這樣實(shí)例能夠互相引用而不產(chǎn)生循環(huán)強(qiáng)引用。

對(duì)于生命周期中會(huì)變?yōu)?code>nil的實(shí)例使用弱引用。相反的,對(duì)于初始化賦值后再也不會(huì)被賦值為nil的實(shí)例,使用無(wú)主引用。

弱引用

弱引用不會(huì)牢牢保持住引用的實(shí)例,并且不會(huì)阻止 ARC 銷(xiāo)毀被引用的實(shí)例。這種行為阻止了引用變?yōu)檠h(huán)強(qiáng)引用。聲明屬性或者變量時(shí),在前面加上weak關(guān)鍵字表明這是一個(gè)弱引用。

在實(shí)例的生命周期中,如果某些時(shí)候引用沒(méi)有值,那么弱引用可以阻止循環(huán)強(qiáng)引用。如果引用總是有值,則可以使用無(wú)主引用,在[無(wú)主引用](#unowned references)中有描述。在上面Apartment的例子中,一個(gè)公寓的生命周期中,有時(shí)是沒(méi)有“居民”的,因此適合使用弱引用來(lái)解決循環(huán)強(qiáng)引用。

注意:
弱引用必須被聲明為變量,表明其值能在運(yùn)行時(shí)被修改。弱引用不能被聲明為常量。

因?yàn)槿跻每梢詻](méi)有值,你必須將每一個(gè)弱引用聲明為可選類(lèi)型。可選類(lèi)型是在 Swift 語(yǔ)言中推薦的用來(lái)表示可能沒(méi)有值的類(lèi)型。

因?yàn)槿跻貌粫?huì)保持所引用的實(shí)例,即使引用存在,實(shí)例也有可能被銷(xiāo)毀。因此,ARC 會(huì)在引用的實(shí)例被銷(xiāo)毀后自動(dòng)將其賦值為nil。你可以像其他可選值一樣,檢查弱引用的值是否存在,你永遠(yuǎn)也不會(huì)遇到被銷(xiāo)毀了而不存在的實(shí)例。

下面的例子跟上面PersonApartment的例子一致,但是有一個(gè)重要的區(qū)別。這一次,Apartmenttenant屬性被聲明為弱引用:

    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { println("\(name) is being deinitialized") }
    }
    class Apartment {
        let number: Int
        init(number: Int) { self.number = number }
        weak var tenant: Person?
        deinit { println("Apartment #\(number) is being deinitialized") }
    }

然后跟之前一樣,建立兩個(gè)變量(john和number73)之間的強(qiáng)引用,并關(guān)聯(lián)兩個(gè)實(shí)例:

    var john: Person?
    var number73: Apartment?
    john = Person(name: "John Appleseed")
    number73 = Apartment(number: 73)
    john!.apartment = number73
    number73!.tenant = john

現(xiàn)在,兩個(gè)關(guān)聯(lián)在一起的實(shí)例的引用關(guān)系如下圖所示:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_4.png" alt="image of Automatic_Reference_Counting_4.png" />

Person實(shí)例依然保持對(duì)Apartment實(shí)例的強(qiáng)引用,但是Apartment實(shí)例只是對(duì)Person實(shí)例的弱引用。這意味著當(dāng)你斷開(kāi)john變量所保持的強(qiáng)引用時(shí),再也沒(méi)有指向Person實(shí)例的強(qiáng)引用了:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_5.png" alt="image of Automatic_Reference_Counting_5.png" />

由于再也沒(méi)有指向Person實(shí)例的強(qiáng)引用,該實(shí)例會(huì)被銷(xiāo)毀:

    john = nil
    // prints "John Appleseed is being deinitialized"

唯一剩下的指向Apartment實(shí)例的強(qiáng)引用來(lái)自于變量number73。如果你斷開(kāi)這個(gè)強(qiáng)引用,再也沒(méi)有指向Apartment實(shí)例的強(qiáng)引用了:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_6.png" alt="image of Automatic_Reference_Counting_6.png" />

由于再也沒(méi)有指向Apartment實(shí)例的強(qiáng)引用,該實(shí)例也會(huì)被銷(xiāo)毀:

    number73 = nil
    // prints "Apartment #73 is being deinitialized"

上面的兩段代碼展示了變量johnnumber73在被賦值為nil后,Person實(shí)例和Apartment實(shí)例的析構(gòu)函數(shù)都打印出“銷(xiāo)毀”的信息。這證明了引用循環(huán)被打破了。

無(wú)主引用

和弱引用類(lèi)似,無(wú)主引用不會(huì)牢牢保持住引用的實(shí)例。和弱引用不同的是,無(wú)主引用是永遠(yuǎn)有值的。因此,無(wú)主引用總是被定義為非可選類(lèi)型(non-optional type)。你可以在聲明屬性或者變量時(shí),在前面加上關(guān)鍵字unowned表示這是一個(gè)無(wú)主引用。

由于無(wú)主引用是非可選類(lèi)型,你不需要在使用它的時(shí)候?qū)⑺归_(kāi)。無(wú)主引用總是可以被直接訪問(wèn)。不過(guò) ARC 無(wú)法在實(shí)例被銷(xiāo)毀后將無(wú)主引用設(shè)為nil,因?yàn)榉强蛇x類(lèi)型的變量不允許被賦值為nil。

注意:
如果你試圖在實(shí)例被銷(xiāo)毀后,訪問(wèn)該實(shí)例的無(wú)主引用,會(huì)觸發(fā)運(yùn)行時(shí)錯(cuò)誤。使用無(wú)主引用,你必須確保引用始終指向一個(gè)未銷(xiāo)毀的實(shí)例。
還需要注意的是如果你試圖訪問(wèn)實(shí)例已經(jīng)被銷(xiāo)毀的無(wú)主引用,程序會(huì)直接崩潰,而不會(huì)發(fā)生無(wú)法預(yù)期的行為。所以你應(yīng)當(dāng)避免這樣的事情發(fā)生。

下面的例子定義了兩個(gè)類(lèi),CustomerCreditCard,模擬了銀行客戶(hù)和客戶(hù)的信用卡。這兩個(gè)類(lèi)中,每一個(gè)都將另外一個(gè)類(lèi)的實(shí)例作為自身的屬性。這種關(guān)系會(huì)潛在的創(chuàng)造循環(huán)強(qiáng)引用。

CustomerCreditCard之間的關(guān)系與前面弱引用例子中ApartmentPerson的關(guān)系截然不同。在這個(gè)數(shù)據(jù)模型中,一個(gè)客戶(hù)可能有或者沒(méi)有信用卡,但是一張信用卡總是關(guān)聯(lián)著一個(gè)客戶(hù)。為了表示這種關(guān)系,Customer類(lèi)有一個(gè)可選類(lèi)型的card屬性,但是CreditCard類(lèi)有一個(gè)非可選類(lèi)型的customer屬性。

此外,只能通過(guò)將一個(gè)number值和customer實(shí)例傳遞給CreditCard構(gòu)造函數(shù)的方式來(lái)創(chuàng)建CreditCard實(shí)例。這樣可以確保當(dāng)創(chuàng)建CreditCard實(shí)例時(shí)總是有一個(gè)customer實(shí)例與之關(guān)聯(lián)。

由于信用卡總是關(guān)聯(lián)著一個(gè)客戶(hù),因此將customer屬性定義為無(wú)主引用,用以避免循環(huán)強(qiáng)引用:

    class Customer {
        let name: String
        var card: CreditCard?
        init(name: String) {
            self.name = name
        }
        deinit { println("\(name) is being deinitialized") }
    }
    class CreditCard {
        let number: Int
        unowned let customer: Customer
        init(number: Int, customer: Customer) {
            self.number = number
            self.customer = customer
        }
        deinit { println("Card #\(number) is being deinitialized") }
    }

下面的代碼片段定義了一個(gè)叫john的可選類(lèi)型Customer變量,用來(lái)保存某個(gè)特定客戶(hù)的引用。由于是可選類(lèi)型,所以變量被初始化為nil

    var john: Customer?

現(xiàn)在你可以創(chuàng)建Customer類(lèi)的實(shí)例,用它初始化CreditCard實(shí)例,并將新創(chuàng)建的CreditCard實(shí)例賦值為客戶(hù)的card屬性。

    john = Customer(name: "John Appleseed")
    john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

在你關(guān)聯(lián)兩個(gè)實(shí)例后,它們的引用關(guān)系如下圖所示:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_7.png" alt="image of Automatic_Reference_Counting_7.png" />

Customer實(shí)例持有對(duì)CreditCard實(shí)例的強(qiáng)引用,而CreditCard實(shí)例持有對(duì)Customer實(shí)例的無(wú)主引用。

由于customer的無(wú)主引用,當(dāng)你斷開(kāi)john變量持有的強(qiáng)引用時(shí),再也沒(méi)有指向Customer實(shí)例的強(qiáng)引用了:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_8.png" alt="image of Automatic_Reference_Counting_8.png" />

由于再也沒(méi)有指向Customer實(shí)例的強(qiáng)引用,該實(shí)例被銷(xiāo)毀了。其后,再也沒(méi)有指向CreditCard實(shí)例的強(qiáng)引用,該實(shí)例也隨之被銷(xiāo)毀了:

    john = nil
    // prints "John Appleseed is being deinitialized"
    // prints "Card #1234567890123456 is being deinitialized"

最后的代碼展示了在john變量被設(shè)為nilCustomer實(shí)例和CreditCard實(shí)例的構(gòu)造函數(shù)都打印出了“銷(xiāo)毀”的信息。

無(wú)主引用以及隱式解析可選屬性

上面弱引用和無(wú)主引用的例子涵蓋了兩種常用的需要打破循環(huán)強(qiáng)引用的場(chǎng)景。

PersonApartment的例子展示了兩個(gè)屬性的值都允許為nil,并會(huì)潛在的產(chǎn)生循環(huán)強(qiáng)引用。這種場(chǎng)景最適合用弱引用來(lái)解決。

CustomerCreditCard的例子展示了一個(gè)屬性的值允許為nil,而另一個(gè)屬性的值不允許為nil,并會(huì)潛在的產(chǎn)生循環(huán)強(qiáng)引用。這種場(chǎng)景最適合通過(guò)無(wú)主引用來(lái)解決。

然而,存在著第三種場(chǎng)景,在這種場(chǎng)景中,兩個(gè)屬性都必須有值,并且初始化完成后不能為nil。在這種場(chǎng)景中,需要一個(gè)類(lèi)使用無(wú)主屬性,而另外一個(gè)類(lèi)使用隱式解析可選屬性。

這使兩個(gè)屬性在初始化完成后能被直接訪問(wèn)(不需要可選展開(kāi)),同時(shí)避免了循環(huán)引用。這一節(jié)將為你展示如何建立這種關(guān)系。

下面的例子定義了兩個(gè)類(lèi),CountryCity,每個(gè)類(lèi)將另外一個(gè)類(lèi)的實(shí)例保存為屬性。在這個(gè)模型中,每個(gè)國(guó)家必須有首都,而每一個(gè)城市必須屬于一個(gè)國(guó)家。為了實(shí)現(xiàn)這種關(guān)系,Country類(lèi)擁有一個(gè)capitalCity屬性,而City類(lèi)有一個(gè)country屬性:

    class Country {
        let name: String
        let capitalCity: City!
        init(name: String, capitalName: String) {
            self.name = name
            self.capitalCity = City(name: capitalName, country: self)
        }
    }
    class City {
        let name: String
        unowned let country: Country
        init(name: String, country: Country) {
            self.name = name
            self.country = country
        }
    }

為了建立兩個(gè)類(lèi)的依賴(lài)關(guān)系,City的構(gòu)造函數(shù)有一個(gè)Country實(shí)例的參數(shù),并且將實(shí)例保存為country屬性。

Country的構(gòu)造函數(shù)調(diào)用了City的構(gòu)造函數(shù)。然而,只有Country的實(shí)例完全初始化完后,Country的構(gòu)造函數(shù)才能把self傳給City的構(gòu)造函數(shù)。(在兩段式構(gòu)造過(guò)程中有具體描述

為了滿足這種需求,通過(guò)在類(lèi)型結(jié)尾處加上感嘆號(hào)(City!)的方式,將CountrycapitalCity屬性聲明為隱式解析可選類(lèi)型的屬性。這表示像其他可選類(lèi)型一樣,capitalCity屬性的默認(rèn)值為nil,但是不需要展開(kāi)它的值就能訪問(wèn)它。(在隱式解析可選類(lèi)型中有描述

由于capitalCity默認(rèn)值為nil,一旦Country的實(shí)例在構(gòu)造函數(shù)中給name屬性賦值后,整個(gè)初始化過(guò)程就完成了。這代表一旦name屬性被賦值后,Country的構(gòu)造函數(shù)就能引用并傳遞隱式的self。Country的構(gòu)造函數(shù)在賦值capitalCity時(shí),就能將self作為參數(shù)傳遞給City的構(gòu)造函數(shù)。

以上的意義在于你可以通過(guò)一條語(yǔ)句同時(shí)創(chuàng)建CountryCity的實(shí)例,而不產(chǎn)生循環(huán)強(qiáng)引用,并且capitalCity的屬性能被直接訪問(wèn),而不需要通過(guò)感嘆號(hào)來(lái)展開(kāi)它的可選值:

    var country = Country(name: "Canada", capitalName: "Ottawa")
    println("\(country.name)'s capital city is called \(country.capitalCity.name)")
    // prints "Canada's capital city is called Ottawa"

在上面的例子中,使用隱式解析可選值的意義在于滿足了兩個(gè)類(lèi)構(gòu)造函數(shù)的需求。capitalCity屬性在初始化完成后,能像非可選值一樣使用和存取同時(shí)還避免了循環(huán)強(qiáng)引用。

閉包引起的循環(huán)強(qiáng)引用

前面我們看到了循環(huán)強(qiáng)引用環(huán)是在兩個(gè)類(lèi)實(shí)例屬性互相保持對(duì)方的強(qiáng)引用時(shí)產(chǎn)生的,還知道了如何用弱引用和無(wú)主引用來(lái)打破循環(huán)強(qiáng)引用。

循環(huán)強(qiáng)引用還會(huì)發(fā)生在當(dāng)你將一個(gè)閉包賦值給類(lèi)實(shí)例的某個(gè)屬性,并且這個(gè)閉包體中又使用了實(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)殚]包和類(lèi)相似,都是引用類(lèi)型。當(dāng)你把一個(gè)閉包賦值給某個(gè)屬性時(shí),你也把一個(gè)引用賦值給了這個(gè)閉包。實(shí)質(zhì)上,這跟之前的問(wèn)題是一樣的-兩個(gè)強(qiáng)引用讓彼此一直有效。但是,和兩個(gè)類(lèi)實(shí)例不同,這次一個(gè)是類(lèi)實(shí)例,另一個(gè)是閉包。

Swift 提供了一種優(yōu)雅的方法來(lái)解決這個(gè)問(wèn)題,稱(chēng)之為閉包占用列表(closuer capture list)。同樣的,在學(xué)習(xí)如何用閉包占用列表破壞循環(huán)強(qiáng)引用之前,先來(lái)了解一下循環(huán)強(qiáng)引用是如何產(chǎn)生的,這對(duì)我們是很有幫助的。

下面的例子為你展示了當(dāng)一個(gè)閉包引用了self后是如何產(chǎn)生一個(gè)循環(huán)強(qiáng)引用的。例子中定義了一個(gè)叫HTMLElement的類(lèi),用一種簡(jiǎn)單的模型表示 HTML 中的一個(gè)單獨(dú)的元素:

    class HTMLElement {
        let name: String
        let text: String?
        lazy var asHTML: () -> String = {
            if let text = self.text {
                return "<\(self.name)>\(text)</\(self.name)>"
            } else {
                return "<\(self.name) />"
            }
        }
        init(name: String, text: String? = nil) {
            self.name = name
            self.text = text
        }
        deinit {
            println("\(name) is being deinitialized")
        }
    }

HTMLElement類(lèi)定義了一個(gè)name屬性來(lái)表示這個(gè)元素的名稱(chēng),例如代表段落的"p",或者代表?yè)Q行的"br"。HTMLElement還定義了一個(gè)可選屬性text,用來(lái)設(shè)置和展現(xiàn) HTML 元素的文本。

除了上面的兩個(gè)屬性,HTMLElement還定義了一個(gè)lazy屬性asHTML。這個(gè)屬性引用了一個(gè)閉包,將nametext組合成 HTML 字符串片段。該屬性是() -> String類(lèi)型,或者可以理解為“一個(gè)沒(méi)有參數(shù),返回String的函數(shù)”。

默認(rèn)情況下,閉包賦值給了asHTML屬性,這個(gè)閉包返回一個(gè)代表 HTML 標(biāo)簽的字符串。如果text值存在,該標(biāo)簽就包含可選值text;如果text不存在,該標(biāo)簽就不包含文本。對(duì)于段落元素,根據(jù)text是"some text"還是nil,閉包會(huì)返回"<p>some text</p>"或者"<p />"。

可以像實(shí)例方法那樣去命名、使用asHTML屬性。然而,由于asHTML是閉包而不是實(shí)例方法,如果你想改變特定元素的 HTML 處理的話,可以用自定義的閉包來(lái)取代默認(rèn)值。

注意:
asHTML聲明為lazy屬性,因?yàn)橹挥挟?dāng)元素確實(shí)需要處理為HTML輸出的字符串時(shí),才需要使用asHTML。也就是說(shuō),在默認(rèn)的閉包中可以使用self,因?yàn)橹挥挟?dāng)初始化完成以及self確實(shí)存在后,才能訪問(wèn)lazy屬性。

HTMLElement類(lèi)只提供一個(gè)構(gòu)造函數(shù),通過(guò)nametext(如果有的話)參數(shù)來(lái)初始化一個(gè)元素。該類(lèi)也定義了一個(gè)析構(gòu)函數(shù),當(dāng)HTMLElement實(shí)例被銷(xiāo)毀時(shí),打印一條消息。

下面的代碼展示了如何用HTMLElement類(lèi)創(chuàng)建實(shí)例并打印消息。

    var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
    println(paragraph!.asHTML())
    // prints"hello, world"

注意:
上面的paragraph變量定義為可選HTMLElement,因此我們可以賦值nil給它來(lái)演示循環(huán)強(qiáng)引用。

不幸的是,上面寫(xiě)的HTMLElement類(lèi)產(chǎn)生了類(lèi)實(shí)例和asHTML默認(rèn)值的閉包之間的循環(huán)強(qiáng)引用。循環(huán)強(qiáng)引用如下圖所示:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_9.png" alt="image of Automatic_Reference_Counting_9.png" />

實(shí)例的asHTML屬性持有閉包的強(qiáng)引用。但是,閉包在其閉包體內(nèi)使用了self(引用了self.nameself.text),因此閉包捕獲了self,這意味著閉包又反過(guò)來(lái)持有了HTMLElement實(shí)例的強(qiáng)引用。這樣兩個(gè)對(duì)象就產(chǎn)生了循環(huán)強(qiáng)引用。(更多關(guān)于閉包捕獲值的信息,請(qǐng)參考值捕獲)。

注意:
雖然閉包多次使用了self,它只捕獲HTMLElement實(shí)例的一個(gè)強(qiáng)引用。

如果設(shè)置paragraph變量為nil,打破它持有的HTMLElement實(shí)例的強(qiáng)引用,HTMLElement實(shí)例和它的閉包都不會(huì)被銷(xiāo)毀,也是因?yàn)檠h(huán)強(qiáng)引用:

    paragraph = nil

注意HTMLElementdeinitializer中的消息并沒(méi)有別打印,證明了HTMLElement實(shí)例并沒(méi)有被銷(xiāo)毀。

解決閉包引起的循環(huán)強(qiáng)引用

在定義閉包時(shí)同時(shí)定義捕獲列表作為閉包的一部分,通過(guò)這種方式可以解決閉包和類(lèi)實(shí)例之間的循環(huán)強(qiáng)引用。捕獲列表定義了閉包體內(nèi)捕獲一個(gè)或者多個(gè)引用類(lèi)型的規(guī)則。跟解決兩個(gè)類(lèi)實(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(而不只是somePropertysomeMethod)。這提醒你可能會(huì)不小心就捕獲了self

定義捕獲列表

捕獲列表中的每個(gè)元素都是由weak或者unowned關(guān)鍵字和實(shí)例的引用(如selfsomeInstance)成對(duì)組成。每一對(duì)都在方括號(hào)中,通過(guò)逗號(hào)分開(kāi)。

捕獲列表放置在閉包參數(shù)列表和返回類(lèi)型之前:

    lazy var someClosure: (Int, String) -> String = {
        [unowned self] (index: Int, stringToProcess: String) -> String in
        // closure body goes here
    }

如果閉包沒(méi)有指定參數(shù)列表或者返回類(lèi)型,則可以通過(guò)上下文推斷,那么可以捕獲列表放在閉包開(kāi)始的地方,跟著是關(guān)鍵字in

    lazy var someClosure: () -> String = {
        [unowned self] in
        // closure body goes here
    }

弱引用和無(wú)主引用

當(dāng)閉包和捕獲的實(shí)例總是互相引用時(shí)并且總是同時(shí)銷(xiāo)毀時(shí),將閉包內(nèi)的捕獲定義為無(wú)主引用。

相反的,當(dāng)捕獲引用有時(shí)可能會(huì)是nil時(shí),將閉包內(nèi)的捕獲定義為弱引用。弱引用總是可選類(lèi)型,并且當(dāng)引用的實(shí)例被銷(xiāo)毀后,弱引用的值會(huì)自動(dòng)置為nil。這使我們可以在閉包內(nèi)檢查它們是否存在。

注意:
如果捕獲的引用絕對(duì)不會(huì)置為nil,應(yīng)該用無(wú)主引用,而不是弱引用。

前面的HTMLElement例子中,無(wú)主引用是正確的解決循環(huán)強(qiáng)引用的方法。這樣編寫(xiě)HTMLElement類(lèi)來(lái)避免循環(huán)強(qiáng)引用:

    class HTMLElement {
        let name: String
        let text: String?
        lazy var asHTML: () -> String = {
            [unowned self] in
            if let text = self.text {
                return "<\(self.name)>\(text)</\(self.name)>"
            } else {
                return "<\(self.name) />"
            }
        }
        init(name: String, text: String? = nil) {
            self.name = name
            self.text = text
        }
        deinit {
            println("\(name) is being deinitialized")
        }
    }

上面的HTMLElement實(shí)現(xiàn)和之前的實(shí)現(xiàn)一致,只是在asHTML閉包中多了一個(gè)捕獲列表。這里,捕獲列表是[unowned self],表示“用無(wú)主引用而不是強(qiáng)引用來(lái)捕獲self”。

和之前一樣,我們可以創(chuàng)建并打印HTMLElement實(shí)例:

    var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
    println(paragraph!.asHTML())
    // prints "<p>hello, world</p>"

使用捕獲列表后引用關(guān)系如下圖所示:

http://wiki.jikexueyuan.com/project/swift-language-guide/images/Automatic_Reference_Counting_10.png" alt="image of Automatic_Reference_Counting_10.png" />

這一次,閉包以無(wú)主引用的形式捕獲self,并不會(huì)持有HTMLElement實(shí)例的強(qiáng)引用。如果將paragraph賦值為nil,HTMLElement實(shí)例將會(huì)被銷(xiāo)毀,并能看到它的析構(gòu)函數(shù)打印出的消息。

    paragraph = nil
    // prints "p is being deinitialized"