swift簡單總結(二十七)—— 自動引用計數

版本記錄

版本號 時間
V1.0 2017.07.31

前言

我是swift2.0的時候開始接觸的,記得那時候還不是很穩定,公司的項目也都是用oc做的,并不對swift很重視,我自己學了一段時間,到現在swift3.0+已經出來了,自己平時也不寫,忘記的也差不多了,正好項目這段時間已經上線了,不是很忙,我就可以每天總結一點了,希望對自己對大家有所幫助。在總結的時候我會對比oc進行說明,有代碼的我會給出相關比對代碼。
1. swift簡單總結(一)—— 數據簡單值和類型轉換
2. swift簡單總結(二)—— 簡單值和控制流
3. swift簡單總結(三)—— 循環控制和函數
4. swift簡單總結(四)—— 函數和類
5. swift簡單總結(五)—— 枚舉和結構體
6. swift簡單總結(六)—— 協議擴展與泛型
7. swift簡單總結(七)—— 數據類型
8. swift簡單總結(八)—— 別名、布爾值與元組
9. swift簡單總結(九)—— 可選值和斷言
10. swift簡單總結(十)—— 運算符
11. swift簡單總結(十一)—— 字符串和字符
12. swift簡單總結(十二)—— 集合類型之數組
13. swift簡單總結(十三)—— 集合類型之字典
14. swift簡單總結(十四)—— 控制流
15. swift簡單總結(十五)—— 控制轉移語句
16. swift簡單總結(十六)—— 函數
17. swift簡單總結(十七)—— 閉包(Closures)
18. swift簡單總結(十八)—— 枚舉
19. swift簡單總結(十九)—— 類和結構體
20. swift簡單總結(二十)—— 屬性
21. swift簡單總結(二十一)—— 方法
22. swift簡單總結(二十二)—— 下標腳本
23. swift簡單總結(二十三)—— 繼承
24. swift簡單總結(二十四)—— 構造過程
25. swift簡單總結(二十五)—— 構造過程
26. swift簡單總結(二十六)—— 析構過程

自動引用計數

OC一樣,swift也有引用自動引用計數,用來管理內存。引用計數僅僅應用于類的實例。結構體和枚舉類型都是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。

主要從下面幾個方向講述。

  • 自動引用計數的工作機制
  • 自動引用計數實踐
  • 類實例之間的循環強引用
  • 解決實例之間循環強引用
  • 閉包引起的循環強引用
  • 解決閉包引起的循環強引用

自動引用計數的工作機制

OC一樣,swift中為了確保使用中的實例不被銷毀,ARC會跟蹤和計算每一個實例正在被多少屬性、常量和變量引用,只要引用計數器不為1,ARC就不會銷毀這個實例。無論是將實例賦值給常量、變量或者屬性,都會對此實例創建強引用,將這個實例牢牢抓住


自動引用計數實踐

先看一個代碼。

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

}

Person類里面定義了構造函數和析構函數。下面定義三個類型為Person的變量,用來按照代碼片段中順序,為新的Person實例建立引用,如下所示:

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

這里要注意,這些變量被定義為可選類型Person?不是Person,它們的值會被自動初始化為nil,目前還不會引用到Person類的實例。

下面我們建立實例。

class JJPracticeVC: UIViewController {
    
    var reference1 : Person?
    var reference2 : Person?
    var reference3 : Person?
    

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        reference1 = Person(name: "John")
        reference2 = Person(name: "Rose")
        reference3 = Person(name: "Nick")
    }
}

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

下面我們看一下

John is being initialized
Rose is being initialized
Nick is being initialized

可見析構函數沒有調用,也就是三個對象都沒有銷毀,想要解除強引用,只需要給相關對象賦值為nil即可。

class JJPracticeVC: UIViewController {
    
    var reference1 : Person?
    var reference2 : Person?
    var reference3 : Person?
    

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        reference1 = Person(name: "John")
        reference2 = Person(name: "Rose")
        reference3 = Person(name: "Nick")
        
        reference2 = nil
        reference3 = nil
    }
}

下面看輸出結果

John is being initialized
Rose is being initialized
Nick is being initialized
Rose is being deinitialized
Nick is being deinitialized

可見,reference2reference3被銷毀了。


類實例之間的循環強引用

循環引用的概念與OC中是一樣的,如果鏈各個類實例之間互相保持對方的強引用,并讓對方不被銷毀,這就是所謂的循環強引用。在swift中解除強引用的方法就是通過定義類之間的關系為弱引用或者無主引用,以此替代強引用,從而解決循環強引用的問題。

下面就給出一個強引用的例子。

class JJPracticeVC: UIViewController {
    
    var john : Person?
    var number : Apartment?

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        john = Person(name: "John")
        number = Apartment(number: 73)
    }
}

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

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

下面看輸出結果

John is being initialized

看一下效果圖。

循環引用效果圖

他們的析構函數都沒有被調用,也就是說他們有了強引用,無法釋放對象。


解決實例之間的循環強引用

swift提供了兩種方法用來解決你在使用類的屬性時所遇到的循環強引用問題:

  • 弱引用weak reference
  • 無主引用 unowned reference

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

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

1. 弱引用

弱引用不會牢牢保持住引用的實例,并且不會阻止ARC銷毀被引用的實例,這種行為阻止了引用變為循環強引用,聲明屬性或者變量時,在前面加上weak關鍵字表明這是一個弱引用。

弱引用還需要注意下面幾點

  • 在實例的生命周期中,如果某些時候引用沒有值,那么弱引用可以阻止循環強引用,如果引用總是有值,則可以使用無主引用。在上面Apartment的例子中,一個公寓的生命周期中,有時候是沒有居民的,所以適合使用弱引用來解決循環強引用。
  • 弱引用必須生命為變量,表明其值能在運行時被修改,弱引用不能被聲明為常量。
  • 因為弱引用可以沒有值,你必須將每一個弱引用聲明為可選類型。

下面看一下例子。

class JJPracticeVC: UIViewController {
    
    var john : Person?
    var number : Apartment?

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        john = nil
        number = nil
        
        john = Person(name: "John")
        number = Apartment(number: 73)
        
        john!.apartment = number
        number?.tenant = john
    }
}

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

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

由于一端用weak修飾,所以現在二者效果如下:

效果圖

可見二者之間不會相互強引用了,現在將他們之間的一個對象設置為nil,二者都會打印銷毀信息,二者引用關系如下所示。

效果圖

2. 無主引用

和弱引用不同的是,無主引用永遠是有值的,因此無主引用總是被定義為非可選類型,你可以在聲明常量或者變量時,在前面加上關鍵字unowned

還要注意:

  • 如果你試圖在實例被銷毀以后,訪問該實例的無主引用,會觸發運行時錯誤,使用無主引用,你必須確保引用始終指向一個未銷毀實例。還有,如果你試圖訪問實例已經被銷毀的無主引用,程序會直接崩潰。

下面定義兩個類CustomerCreditCard,但是這個和前面公寓和人的關系不一樣,這個模型中,一個客戶可能有也可能沒有信用卡,但是一個信用卡一定關系一個客戶,所以Customer類有一個可選類型card屬性,但是CreditCard類有一個非可選類型的customer屬性。

下面看一下代碼。

class JJPracticeVC: UIViewController {
    
    var john : Customer?

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        john = Customer(name: "John")
        john!.card = CreditCard(number: 1234_5678_2333, customer: john!)
    }
}

class Customer {
    let name : String
    var card : CreditCard?
    init(name : String) {
        self.name = name
    }
    deinit {
        print("\(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 {
        print("Card \(number) is being deinitialized")
    }
}

這里,Customer實例持有對CreditCard實例的強引用,而CreditCard實例持有對Customer的無主引用,具體如下所示。

關系圖

由于Customer的無主引用,當你斷開john變量持有的強引用時,再也沒有指向customer實例的強引用了,如下圖所示。

無主引用
class JJPracticeVC: UIViewController {
    
    var john : Customer?

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        john = Customer(name: "John")
        john!.card = CreditCard(number: 1234_5678_2333, customer: john!)
        
        john = nil
    }
}

加一句john = nil,我們看一下輸出結果

John is being deinitialized
Card 123456782333 is being deinitialized

可見二者都被釋放了。

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

上面打破循環引用給出了兩種情況“

  • PersonApartment展示了兩個屬性的值都允許為nil,并可能產生循環引用,這種情況適合采用弱引用。
  • CustomerCreditCard展示了一個屬性的值可以為nil,另外一個不可以為nil的情況,并可能產生循環引用,這種情況適合采用無主引用。

還存在另外一種情況:兩個屬性都必須有值,并且初始化完成后不能為nil,在這種情況下,需要一個類使用無主屬性,另外一個類使用隱式解析可選類型。

下面看一個簡單例子,每一個Country國家都要有首都city,每一個城市也必須屬于一個國家。

class Country {
    let name : String
    var 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
    }
}

上面的意義在于你可以通過一條語句同時創建CityCountry的實例,而不產生循環引用,并且capitalCity的屬性能被直接訪問,而不需要通過感嘆號來展開它的可選值。


閉包引起的循環強引用

這種情況有點類似OC中的block引用,循環強引用還會發生在當你將一個閉包賦值給類實例的某個屬性,并且這個閉包體中又使用了實例,這就容易引起循環引用。

swift提供了一種優雅的方式解決這個問題,稱為閉包占用列表(closure capture list),下面我們看一下閉包中循環引用是如何產生的。

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 {
        print("\(name) is being deinitialized")
    }
}

注意:這里asHTML聲明為lazy屬性,因為只有當元素確實需要處理為HTML輸出時,才需要使用asHTML,也就是說,在默認的閉包中可以使用self,因為只有當初始化完成以及self確實存在后,才能訪問lazy屬性。

下面調用一下

 var paragraph : HTMLElement? = HTMLElement(name: "p", text: "Hello,world")

下面看一下循環引用的示意圖。

循環引用

這里即使將paragraph = nil設置,也不會調用析構器。


解決閉包引起的循環引用

??在定義閉包時同時定義捕獲列表作為閉包的一部分,通過這種方式可以解決閉包和類實例之間的循環引用,捕獲列表定義了閉包體內捕獲一個或者多個類型的規則,跟解決兩個類實例間的循環強引用一樣,聲明每個捕獲的引用為弱引用或者無主引用,具體選擇哪個根據代碼關系確定。

注意swift要求只要在閉包內使用 self的成員,就要用self.someProperty或者self.someMethod

1. 定義捕獲列表

捕獲列表中的每個元素都是由weak或者unowned關鍵字和實例的引用成對組成,每一對都在方括號中,通過逗號分開。

捕獲列表放在閉包函數參數列表和返回類型之前。

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

如果閉包沒有指定參數列表或返回類型,則可以通過上下文判斷,那么可以捕獲列表放在閉包開始的地方,跟著關鍵字in

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

2. 弱引用和無主引用

當閉包和捕獲的實例總是互相引用時并且總是同時銷毀時,將閉包內的捕獲定義為無主引用,相反,當捕獲引用有時可能會是nil時,將閉包內的捕獲定義為弱引用,弱引用總是可選類型。并且當引用的實例被銷毀后,弱引用會被自動設置為nil,這使我們可以在閉包內檢查它們是否存在。

注意如果捕獲的引用絕對不會置為nil,應該用無主引用,而不是弱引用。

下面看一下簡單例子。


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 {
        print("\(name) is being deinitialized")
    }
}

這樣子就解除了閉包引起的循環引用。

后記

未完,待續~~~~

好多彎
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容