【Swift學習】Swift編程之旅---ARC(二十),swift---arc

來源:互聯網
上載者:User

【Swift學習】Swift編程之旅---ARC(二十),swift---arc

  Swift使用自動引用計數(ARC)來跟蹤並管理應用使用的記憶體。大部分情況下,這意味著在Swift語言中,記憶體管理"仍然工作",不需要自己去考慮記憶體管理的事情。當執行個體不再被使用時,ARC會自動釋放這些類的執行個體所佔用的記憶體。然而,在少數情況下,為了自動的管理記憶體空間,ARC需要瞭解關於你的程式碼片段之間關係的更多資訊。本章描述了這些情況,並向大家展示如何開啟ARC來管理應用的所有記憶體空間。

注意:引用計數只應用在類的執行個體。結構體(Structure)和枚舉類型是實值型別,並非參考型別,不是以引用的方式來儲存和傳遞的。    How ARC Works  每次建立一個類的執行個體,ARC就會分配一個記憶體塊,用來儲存這個執行個體的相關資訊。這個記憶體塊儲存著執行個體的類型,以及這個執行個體相關的屬性的值。當執行個體不再被使用時,ARC釋放這個執行個體使用的記憶體,使這塊記憶體可作它用。這保證了類執行個體不再被使用時,它們不會佔用記憶體空間。但是,如果ARC釋放了仍在使用的執行個體,那麼你就不能再訪問這個執行個體的屬性或者調用它的方法。如果你仍然試圖訪問這個執行個體,應用極有可能會崩潰。為了保證不會發生上述的情況,ARC跟蹤與類的執行個體相關的屬性、常量以及變數的數量。只要有一個有效引用,ARC都不會釋放這個執行個體。   為了讓這變成現實,只要你將一個類的執行個體賦值給一個屬性或者常量或者變數,這個屬性、常量或者變數就是這個執行個體的強引用(strong reference)。之所以稱之為“強”引用,是因為它強持有這個執行個體,並且只要這個強引用還存在,就不能銷毀執行個體。 下面的例子展示了ARC是如何工作的。本例定義了一個簡單的類,類名是Person,並定義了一個名為name的常量屬性
class Person {     let name: String      init(name: String) {         self.name = name         println("\(name) is being initialized")     }        deinit {         println("\(name) is being deinitialized")     } }

 

接下來的程式碼片段定義了三個Person?類型的變數,這些變數用來建立多個引用,這些引用都引用緊跟著的代碼所建立的Person對象。因為這些變數都是可選類型(Person?,而非Person),因此他們都被自動初始化為nil,並且當前並沒有引用一個Person的執行個體。
var reference1: Person? var reference2: Person? var reference3: Person? 

 現在我們建立一個新的Person執行個體,並且將它賦值給上述三個變數中的一個:

reference1 = Person(name: "John Appleseed") // prints "Jonh Appleseed is being initialized" 
因為Person的執行個體賦值給了變數reference1,所以reference1是Person執行個體的強引用。又因為至少有這一個強引用,ARC就保證這個執行個體會儲存在記憶體重而不會被銷毀。 如果將這個Person執行個體賦值給另外的兩個變數,那麼將建立另外兩個指向這個執行個體的強引用:
reference2 = reference1 reference3 = reference2 

 

現在,這一個Person執行個體有三個強引用。 如果你通過賦值nil給兩個變數來破壞其中的兩個強引用(包括原始的引用),只剩下一個強引用,這個Person執行個體也不會被銷毀:

 

reference1 = nil reference2 = nil 

直到第三個也是最後一個強引用被破壞,ARC才會銷毀Person的執行個體,這時,有一點非常明確,你無法繼續使用Person執行個體:

referenece3 = nil // 列印 “John Appleseed is being deinitialized” 

 

                                         

   類執行個體之間的強引用迴圈   在兩個類執行個體彼此保持對方的強引用,使得每個執行個體都使對方保持有效時會發生這種情況。我們稱之為強引用迴圈。 下面的例子展示了一個強引用環是如何在不經意之間產生的。例子定義了兩個類,分別叫Person和Apartment,這兩個類建模了一座公寓以及它的居民:  
class Person {     let name: String     init(name: String) { self.name = name }     var apartment: Apartment?     deinit { println("\(name) is being deinitialized") } }   class Apartment {     let unit: Int     init(unit: Int) { self.unit= unit }     var tenant: Person?     deinit { println("Apartment #\(number) is being deinitialized") } } 

 

每個Person執行個體擁有一個String類型的name屬性以及一個被初始化為nil的apartment可選屬性。apartment屬性是可選的,因為一個人並不一定擁有一座公寓。 類似的,每個Apartment執行個體擁有一個Int類型的number屬性以及一個初始化為nil的tenant可選屬性。tenant屬性是可選的,因為一個公寓並不一定有居民。 這兩個類也都定義了初始化函數,列印訊息表明這個類的執行個體正在被初始化。這使你能夠看到Person和Apartment的執行個體是否像預期的那樣被銷毀了。 下面的程式碼片段定義了兩個可選類型變數,john和number73,分別被賦值為特定的Apartment和Person的執行個體。得益於可選類型的優點,這兩個變數初始值均為nil: 
var john: Person? var unit4A: Apartment?

 現在,你可以建立特定的Person執行個體以及Apartment執行個體,並賦值給john和number73:

jhon = Person(name: "John Appleseed") unit4A = Apartments(number: 4A) 

 下面的圖表明了在建立以及賦值這兩個執行個體後強引用的關係。john擁有一個Person執行個體的強引用,unit4A擁有一個Apartment執行個體的強引用:

 現在你可以將兩個執行個體關聯起來,一個人擁有一所公寓,一個公寓也擁有一個房客。注意:用驚嘆號(!)來展開並訪問可選類型的變數,只有這樣這些變數才能被賦值:

john!.apartment = unit4Aunit4A!.tenant = john

 

 執行個體關聯起來後,強參考關聯性如所示

 

 關聯這倆執行個體產生了一個強循環參考,Person執行個體和Apartment執行個體各持有一個對方的強引用。因此,即使你破壞john和number73所持有的強引用,引用計數也不會變為0,因此ARC不會銷毀這兩個執行個體

 

john = nilunit4A = nil

當上面兩個變數賦值為nil時,沒有調用任何一個析構方法。強引用阻止了Person和Apartment執行個體的銷毀,進一步導致記憶體流失。

 

  避免強引用迴圈

  Swift提供兩種方法避免強引用迴圈:弱引用和非持有引用。 

   對於生命週期中引用會變為nil的執行個體,使用弱引用;對於初始化時賦值之後引用再也不會賦值為nil的執行個體,使用非持有引用。

 

  弱引用

  弱引用不會增加執行個體的引用計數,因此不會阻止ARC銷毀被引用的執行個體,聲明屬性或者變數的時候,關鍵字weak表明引用為弱引用。弱引用只能聲明為變數類型,因為運行時它的值可能改變。弱引用絕對不能聲明為常量

   因為弱引用可以沒有值,所以聲明弱引用的時候必須是可選類型的。在Swift語言中,推薦用可選類型來作為可能沒有值的引用的類型。

 下面的例子和之前的Person和Apartment例子相似,除了一個重要的區別。這一次,我們聲明Apartment的tenant屬性為弱引用:

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

 

 然後建立兩個變數(john和unit4A)的強引用,並關聯這兩個執行個體:

var john: Person?var unit4A: Apartment? john = Person(name: "John Appleseed")unit4A = Apartment(unit: "4A") john!.apartment = unit4Aunit4A!.tenant = john

 

 下面是引用的關係圖:

 

Person的執行個體仍然是Apartment執行個體的強引用,但是Apartment執行個體則是Person執行個體的弱引用。這意味著當破壞john變數所持有的強引用後,不再存在任何Person執行個體的強引用: 

 

 既然不存在Person執行個體的強引用,那麼該執行個體就會被銷毀:

 

    非持有引用 和弱引用相似,非持有引用也不強持有執行個體。但是和弱引用不同的是,非持有引用預設始終有值。因此,非持有引用只能定義為非可選類型(non-optional type)。在屬性、變數前添加unowned關鍵字,可以聲明一個非持有引用。 因為是非可選類型,因此當使用非持有引用的時候,不需要展開,可以直接存取。不過非可選類型變數不能賦值為nil,因此當執行個體被銷毀的時候,ARC無法將引用賦值為nil。  
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") } 

 

下面的代碼定義了一個叫john的可選類型Customer變數,用來儲存某個特定消費者的引用。因為是可變類型,該變數的初始值為nil: 
var john: Customer?

 現在建立一個Customer執行個體,然後用它來初始化CreditCard執行個體,並把剛建立出來的CreditCard執行個體賦值給Customer的card屬性:

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

 此時的參考關聯性如所示

因為john對CreditCard執行個體是非持有引用,當破壞john變數持有的強引用時,就沒有Customer執行個體的強引用了

此時Customer執行個體被銷毀。然後,CreditCard執行個體的強引用也不複存在,因此CreditCard執行個體也被銷毀

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

  

  非持有引用以及隱式展開的可選屬性

Person和Apartment的例子說明了下面的情境:兩個屬性的值都可能是nil,並有可能產生強引用環。這種情境下適合使用弱引用。 Customer和CreditCard的例子則說明了另外的情境:一個屬性可以是nil,另外一個屬性不允許是nil,並有可能產生強引用環。這種情境下適合使用無主引用。 但是,存在第三種情境:兩個屬性都必須有值,且初始化完成後不能為nil。這種情境下,則要一個類用無主引用屬性,另一個類用隱式展開的可選屬性。這樣,在初始化完成後我們可以立即訪問這兩個變數(而不需要可選展開)

 下面的例子定義了兩個類,Country和City,都有一個屬性用來儲存另外的類的執行個體。在這個模型裡,每個國家都有首都,每個城市都隸屬於一個國家。所以,類Country有一個capitalCity屬性,類City有一個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     } } 
City的初始化函數有一個Country執行個體參數,並且用country屬性來儲存這個執行個體。這樣就實現了上面說的關係。 Country的初始化函數調用了City的初始化函數。但是,只有Country的執行個體完全初始化完後(在Two-Phase Initialization),Country的初始化函數才能把self傳給City的初始化函數。 為滿足這種需求,通過在類型結尾處加驚嘆號(City!),我們聲明Country的capitalCity屬性為隱式展開的可選類型屬性。就是說,capitalCity屬性的預設值是nil,不需要展開它的值(在Implicity Unwrapped Optionals中描述)就可以直接存取。 因為capitalCity預設值是nil,一旦Country的執行個體在初始化時給name屬性賦值後,整個初始化過程就完成了。這代表只要賦值name屬性後,Country的初始化函數就能引用並傳遞隱式的self。所以,當Country的初始化函數在賦值capitalCity時,它也可以將self作為參數傳遞給City的初始化函數。 綜上所述,你可以在一條語句中同時建立Country和City的執行個體,卻不會產生強引用環,並且不需要使用驚嘆號來展開它的可選值就可以直接存取capitalCity:
var country = Country(name: "Canada", capitalName: "Ottawa") println("\(country.name)'s captial city is called \(country.capitalCity.name)") // 列印"Canada's capital city is called Ottawa" 

 

 在上面的例子中,使用隱式展開的可選值滿足了兩個類的初始化函數的要求。初始化完成後,capitalCity屬性就可以做為非可選實值型別使用,卻不會產生強引用環。

 

  閉包的強引用迴圈

將一個閉包賦值給類執行個體的某個屬性,並且這個閉包使用了執行個體,這樣也會產生強引用環。這個閉包可能訪問了執行個體的某個屬性,例如self.someProperty,或者調用了執行個體的某個方法,例如self.someMethod。這兩種情況都導致了閉包使用self,從而產生了搶引用環。 因為諸如類這樣的閉包是參考型別,導致了強引用環。當你把一個閉包賦值給某個屬性時,你也把一個引用賦值給了這個閉包。實質上,這個之前描述的問題是一樣的-兩個強引用讓彼此一直有效。但是,和兩個類執行個體不同,這次一個是類執行個體,另一個是閉包。 Swift提供了一種優雅的方法來解決這個問題,我們稱之為閉包捕獲列表(closuer capture list)。 下面的例子將會告訴你當一個閉包引用了self後是如何產生一個強引用迴圈的。
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定義了一個name屬性來表示這個元素的名稱,例如代表段落的"p",或者代表換行的"br";以及一個可選屬性text,用來設定HTML元素的文本。 除了上面的兩個屬性,HTMLElement還定義了一個lazy屬性asHTML。這個屬性引用了一個閉包,將name和text組合成HTML字串片段。該屬性是() -> String類型,就是“沒有參數,返回String的函數”。 預設將閉包賦值給了asHTML 屬性,這個閉包返回一個代表HTML標籤的字串。如果text值存在,該標籤就包含可選值text;或者不包含文本。對於段落,根據text是"some text"還是nil,閉包會返回"<p>some text</p>"或者"<p />"。 可以像執行個體方法那樣去命名、使用asHTML。然而,因為asHTML終究是閉包而不是執行個體方法,如果你像改變特定元素的HTML處理的話,可以用定製的閉包來取代預設值。

 閉包使用了self(引用了self.name和self.text),因此閉包佔有了self,這意味著閉包又反過來持有了HTMLElement執行個體的強引用。這樣就產生了強引用環

 

  避免閉包產生的強引用迴圈

在定義閉包時同時定義捕獲列表作為閉包的一部分,可以解決閉包和類執行個體之間的強引用環。捕獲列表定義了閉包內佔有一個或者多個參考型別的規則。和解決兩個類執行個體間的強引用環一樣,聲明每個佔有的引用為弱引用或非持有引用,而不是強引用。根據代碼關係來決定使用弱引用還是非持有引用。 注意:Swift有如下約束:只要在閉包內使用self的成員,就要用self.someProperty或者self.someMethod(而非只是someProperty或someMethod)。這可以提醒你可能會不小心就佔有了self。

 

   定義捕獲列表

 

捕獲列表中的每個元素都是由weak或者unowned關鍵字和執行個體的引用(如self或someInstance)組成。每一對都在花括弧中,通過逗號分開。 捕獲列表放置在閉包參數列表和傳回型別之前:
lazy var someClosure: (Int, String) -> String = {     [unowned self] (index: Int, stringToProcess: String) -> String in     // closure body goes here } 

 

 如果閉包沒有指定參數列表或者傳回型別(可以通過上下文推斷),那麼佔有列表放在閉包開始的地方,跟著是關鍵字in:

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

 前面提到的HTMLElement例子中,非持有引用是正確的解決強引用的方法。這樣編碼HTMLElement類來避免強引用環:

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實現和之前的實現相同,只是多了佔有列表。這裡,佔有列表是[unowned self],代表“用無主引用而不是強引用來佔有self”。 和之前一樣,我們可以建立並列印HTMLElement執行個體:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") println(paragraph!.asTHML()) // 列印"<p>hello, world</p>" 

 參考關聯性如

這一次,閉包以無主引用的形式佔有self,並不會持有HTMLElement執行個體的強引用。如果賦值paragraph為nil,HTMLElement執行個體將會被銷毀,並能看到它的deinitializer列印的訊息。   
paragraph = nil // 列印"p is being deinitialized" 

 

  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.