Swift基礎10

自動引用計數(shù)

swift使用自動引用計數(shù)(ARC)機制來跟蹤和管理你的應用程序的內(nèi)存。通常情況下,Swift內(nèi)存管理機制會一直起作用,我們無須自己來考慮內(nèi)存的管理。ARC會在類的實例不再被使用時,自動釋放其占用的內(nèi)存。

note:引用計數(shù)僅僅應用于類的實例。結(jié)構(gòu)體和枚舉類型是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。

自動引用計數(shù)的工作機制

為了確保使用的實例不會被銷毀,ARC會跟蹤和計算每一個實例正在被多數(shù)屬性,常量和變量所引用。哪怕實例的引用數(shù)為1,ARC都不會銷毀這個實例。

為了使上述成為可能,無論你將實例賦值給屬性、常量或變量,它們都會創(chuàng)建此實例的強引用。之所以稱為“強”引用,是因為它會將實例牢牢的保持住,只要強引用還在,實例就不允許被銷毀的。

類實例之間的循環(huán)強引用

我們可能會寫出一個類實例的強引用數(shù)永遠不能變成0的代碼。如果兩個類實例互相持有對方的強引用,因而每個實例都讓對方一直存在,就是這種情況。這就是所謂的循環(huán)強引用。

我們可以通過定義類之間的關系為弱引用或無主引用,以替代強引用,從而解決循環(huán)強引用的問題。

class A{
    
    let name:String
    init(name:String){
        self.name = name
    }
    var b:  B?
    
    deinit{
        print("a \(name) is dead")
    }
}

class B {
    let name:String
    init(name:String) {
        self.name = name
    }
    var a:A?
    deinit{
        print("b \(name) is dead")
    }
}

func test(){
    let a = A(name: "obj a")
    let b = B(name: "obj b")
    
    a.b = b
    b.a = a
}

test()

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

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

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

對于生命周期中會變?yōu)閚il的實例使用弱引用。相反地,對于初始化賦值后再也不會被賦值為nil的實例,使用無主引用。

弱引用

弱引用不會對引用的實例保持強引用,因而不會阻止ARC銷毀被引用的實例。這個特性阻止了引用變?yōu)檠h(huán)強引用。聲明屬性或者變量時,在前面加上weak關鍵字聲明這是一個弱引用。

在實例的生命周期中,如果某些時候引用沒有值,哪么弱引用可以避免循環(huán)強引用。如果引用總是有值,則可以使用無主引用。

note: 弱引用必須被聲明為變量,表明其值能在運行時被修改。弱引用不能被聲明為常量。 弱引用可以沒有值,我們必須將每一個弱引用聲明為可選類型。在Swift中,推薦使用可選類型描述可能沒有值的類型。

因為弱引用不會保持所引用的實例,即使引用存在,實例也有可能被銷毀。因此,ARC會在引用的實例被銷毀后自動將其賦值為nil。


class A{
    
    let name:String
    init(name:String){
        self.name = name
    }
    var b:  B?
    
    deinit{
        print("a \(name) is dead")
    }
}

class B {
    let name:String
    init(name:String) {
        self.name = name
    }
    weak  var a:A?
    deinit{
        print("b \(name) is dead")
    }
}

func test(){
    let a = A(name: "obj a")
    let b = B(name: "obj b")
    
    a.b = b
    b.a = a
}

test()

無主引用

和弱引用類似,無主引用不會牢牢保持住引用的實例。和弱引用不同的是,無主引用是永遠有值的。因此,無主引用總被定義為非可選類型。我們可以在聲明屬性或者變量時,在前面添加關鍵字unowned表示這是一個無主引用。

由于無主引用是非可選類型,我們不需要在使用它的時候?qū)⑵湔归_。無主引用總是可以被直接訪問。不過ARC無法在實例被銷毀后將無主引用設為nil,因為非可選類型的變量不允許被賦值為nil。

note:如果我們試圖在實例被銷毀后,訪問該實例的無主引用,會觸發(fā)運行時錯誤。使用無主引用,我們必須確保引用始終指向一個未銷毀的實例。
還需要注意的是如果我們試圖訪問實例已經(jīng)被銷毀的無主引用,Swift確保程序會直接奔潰,而不會發(fā)生無法預期的行為。所以我們應當避免這樣的事情發(fā)生。

class A{
    
    let name:String
    init(name:String){
        self.name = name
    }
    var b:  B?
    
    deinit{
        print("a \(name) is dead")
    }
}

class B {
    let name:String
    init(name:String,a:A ) {
        self.name = name
        self.a = a
    }
    unowned var a:A
    deinit{
        print("b \(name) is dead")
    }
}


func test(){
    let a = A(name: "obj a")
    let b = B(name: "obj b",a:a)
    
    
}

test()

無主引用以及隱式解析可選屬性

還存在著第三種場景,在這種場景中,兩個屬性都必須有值,并且初始化完后永遠不會為nil。在這種場景中,需要一個類使用無主屬性,而另外一個類使用隱式解析可選屬性。

這使兩個屬性在初始化完成后被直接訪問(不需要可選展開),同時避免了循環(huán)引用。

class Country {
    
    let name: String
    var capitalCity: City!
    
    init(name:String,capitalCityName:String){
        self.name = name
        self.capitalCity = City(name: capitalCityName, country: self)
    }
    
    deinit{
        print("country dead")
    }
    
}

class City{
    
    let name:String
    unowned let country: Country
    
    init(name:String,country:Country){
        self.name = name
        self.country = country
    }
    deinit{
        print("city dead")
    }
}

func test(){
    var country = Country(name: "china", capitalCityName: "beijing")
    let city = country.capitalCity
    print("\(country.name) and \(country.capitalCity.name)")
    print("\(city.country.name)and \(city.country.capitalCity.name) ")
}

test()

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

循環(huán)強引用還會發(fā)生在當我們將一個閉包賦值給類實例的某個屬性,并且這個閉包中又使用了這個類的實例。這個閉包中可能訪問了實例的某個屬性(self.someProperty),或者閉包中調(diào)用了實例的某個方法(self.someMethod),這兩種情況都導致閉包捕獲self,從而產(chǎn)生了循環(huán)強引用。

循環(huán)強引用的產(chǎn)生,是因為閉包和類相似,都是引用類型。當我們把一個閉包賦值給某個屬性時,你也把一個引用賦值給這個閉包。實質(zhì)上,這跟之前的問題一樣的---兩個強引用讓彼此一有效。

class HTMLElement{
    
    let name:String
    let text:String?
    
    lazy var asHTML: Void ->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{
        print("\(name) is dead")
    }
    
}


func test(){
    var  html = HTMLElement(name: "h1", text: "hello world!")
    print(html.asHTML())
}

test()

note:雖然閉包多次使用了self,它只捕獲HTMLElement實例的一個強引用。

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

在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和實例之間的循環(huán)強引用。捕獲列表定義了閉包體內(nèi)捕獲一個或多個引用類型的規(guī)則。跟解決兩個類實例間的循環(huán)強引用一樣,聲明每個捕獲的引用為弱引用或無主引用,而不是強引用。應當根據(jù)代碼關系來決定使用弱引用還是無主引用。

note:swift有如下要求:只要閉包內(nèi)使用self的成員,就要用self.someproperty或者self.someMethod(),而不是someproperty或someMethod()。這提醒我們可能一不小心就捕獲了self。

定義捕獲列表

捕獲列表中的每一項都由一個對元素組成,一個元素是weak或unowned關鍵字,另外一個元素是類實例的引用(如self)或初始化過的變量(如delegate = self.delegate?。_@些項在放括號中用逗號分開。

class HTMLElement{
    
    let name:String
    let text:String?
    
    lazy var asHTML: Void ->String = {
        
        [weak weakSelf = self] in
        
        if let text = weakSelf!.text {
            return "<\(weakSelf!.name)> \(text)</\(weakSelf!.name)>"
        }else{
            return "</\(weakSelf!.name)>"
        }
        
    }
    
    init(name:String,text:String? = nil){
        self.name = name
        self.text = text
    }
    
    deinit{
        print("\(name) is dead")
    }
    
}


func test(){
    var  html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
    print(html!.asHTML())
    html = nil
}

test()

如果閉包有參數(shù)列表或返回類型,把捕獲列表放在它們前面:

lazy var someClosure: (Int,String)->String = {
        
        [unowned self,weak delegate = self.delegate!] (index:Int,StringToProcess:String)-> String in
        
        //closure statement
        
    }
    ```

如果閉包沒有指明參數(shù)列表或者返回類型,即它們會通過上下文推斷,哪么可以把捕獲列表和關鍵字in放在閉包最開始的地方:

lazy var someClosure: Void ->String = {

    [unowned self,weak delegate = self.delegate!]  in
    
    //closure statement
    
}
```

弱引用和無主引用

在閉包和捕獲的實例總是相引用時并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為無主引用。

相反的,在捕獲的引用可能會變?yōu)閚il時,將閉包內(nèi)的捕獲定義為弱引用。弱引用總是可選類型,并且當引用的實例被銷毀后,弱引用的值會被自動設置為nil。這使我們可以在閉包體內(nèi)檢查它們是否存在。

note:如果被捕獲的引用絕對不會變?yōu)閚il,應該用無主引用,而不是弱引用。

class HTMLElement{
    
    let name:String
    let text:String?
    
    lazy var asHTML: Void ->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{
        print("\(name) is dead")
    }
    
}


func test(){
    var  html:HTMLElement? = HTMLElement(name: "h1", text: "hello world!")
    print(html!.asHTML())
    html = nil
}

test()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容