Swift--內(nèi)存管理

  • Swift內(nèi)存管理概述
  • 強(qiáng)引用循環(huán)
  • 打破強(qiáng)引用循環(huán)
  • 閉包中的強(qiáng)引用循環(huán)

Swift內(nèi)存管理概述

  • Swift中的ARC內(nèi)存管理是對(duì)引用類(lèi)型的管理,即對(duì)類(lèi)所創(chuàng)建的對(duì)象采用ARC管理。而對(duì)于值類(lèi)型,如整型、浮點(diǎn)型、布爾型、字符串、元組、集合、枚舉和結(jié)構(gòu)體等,是由處理器自動(dòng)管理的,程序員不需要管理它們的內(nèi)存。

  • ARC內(nèi)存管理和值類(lèi)型內(nèi)存管理有一定的區(qū)別。雖然兩者都不需要程序員管理,但本質(zhì)上是有區(qū)別的,ARC和MRC一樣都是針對(duì)引用類(lèi)型的管理,引用類(lèi)型與Objective-C中的對(duì)象指針類(lèi)型一樣,它們的內(nèi)存分配區(qū)域是在“ 堆”上的,需要人為管理。而值類(lèi)型內(nèi)存分配區(qū)域是在“?!鄙系?,由處理器管理,不需要人為管理。

引用計(jì)數(shù)

每個(gè)Swift類(lèi)創(chuàng)建的對(duì)象都有一個(gè)內(nèi)部計(jì)數(shù)器,這個(gè)計(jì)數(shù)器跟蹤對(duì)象的引用次數(shù),稱(chēng)為引用計(jì)數(shù)(Reference Count, 簡(jiǎn)稱(chēng)RC)。

class Employee {
    var no: Int
    var name: String
    var job: String
    var salary: Double

    init(no: Int, name: String, job: String, salary: Double) {
        self.no = no
        self.name = name
        self.job = job
        self.salary = salary
        print("員工\(name) 已經(jīng)構(gòu)造成功。")
    }
    deinit {
        print("員工\(name) 已經(jīng)析構(gòu)成功。")
    }
}

var ref1: Employee?
var ref2: Employee?
var ref3: Employee?

ref1 = Employee(no: 7698, name: "Lucy", job: "teacher", salary: 4000) //RC = 1

ref2 = ref1
ref3 = ref1

ref1 = nil // RC = 2
ref2 = nil // RC = 1
ref3 = nil // RC = 0
強(qiáng)引用循環(huán)

當(dāng)兩個(gè)對(duì)象的存儲(chǔ)屬性互相引用對(duì)方的時(shí)候,一個(gè)對(duì)象釋放的前提是對(duì)方先釋放,另一對(duì)象釋放的前提也是對(duì)方先釋放,這樣就會(huì)導(dǎo)致類(lèi)似于“死鎖”的狀態(tài),最后誰(shuí)都不能釋放,導(dǎo)致內(nèi)存泄漏。這種現(xiàn)象就是強(qiáng)引用循環(huán)。

如下圖:Employee 和Department互相引用


image.png

class Employee {
    var no: Int
    var name: String
    var job: String
    var salary: Double
    var dept: Department?


    init(no: Int, name: String, job: String, salary: Double) {
        self.no = no
        self.name = name
        self.job = job
        self.salary = salary
        print("員工\(name) 已經(jīng)構(gòu)造成功。")
    }
    deinit {
        print("員工\(name) 已經(jīng)析構(gòu)成功。")
    }
}

class Department {
    var no: Int = 0
    var name: String = ""
    var manager: Employee?

    init(no: Int, name: String) {
        self.no = no
        self.name = name
        print("部門(mén)\(name) 已經(jīng)構(gòu)造成功。")
    }
    deinit {
        print("部門(mén)\(name) 已經(jīng)析構(gòu)成功。")
    }
}

var emp: Employee?
var dept: Department?

emp = Employee(no: 3041, name: "Lucy", job: "teacher", salary: 5000) //RC = 1
dept = Department(no: 28, name: "Lily") // RC = 1

emp!.dept = dept //RC = 2
dept!.manager = emp // RC = 2

emp = nil //RC = 1
dept = nil // RC = 1

如上代碼:類(lèi)Employee中的deinit方法和Department中的deinit方法都沒(méi)有被調(diào)用,說(shuō)明對(duì)象emp和對(duì)象dept都沒(méi)有被釋放掉。

emp 與dept對(duì)象之前建立關(guān)系
emp 和 dept強(qiáng)引用斷開(kāi)

image.png

對(duì)象釋放的前提是沒(méi)有指向它的強(qiáng)引用,也就是它的引用計(jì)數(shù)為0。例如:指向Employee對(duì)象的強(qiáng)引用原本有兩個(gè)(1#和4#),也就是引用計(jì)數(shù)為2;當(dāng)emp = nil 斷開(kāi)1#強(qiáng)引用,引用計(jì)數(shù)為1;由于強(qiáng)引用循環(huán)4#強(qiáng)引用一直不能斷開(kāi),引用計(jì)數(shù)不能為0,導(dǎo)致Employee對(duì)象不能釋放。

Employee對(duì)象和Department對(duì)象都沒(méi)有被釋放,這就是強(qiáng)引用循環(huán),會(huì)導(dǎo)致內(nèi)存泄漏。

打破強(qiáng)引用循環(huán)

強(qiáng)引用循環(huán)的危害非常大,所以我們要打破強(qiáng)引用循環(huán),打破強(qiáng)引用循環(huán)的方法有兩種

  • 弱引用( weak reference)
  • 無(wú)主引用 (unowned reference)
弱引用

弱引用允許循環(huán)引用中的一個(gè)對(duì)象不采用強(qiáng)引用方式引用另一個(gè)對(duì)象,這樣就不會(huì)引起強(qiáng)引用循環(huán)問(wèn)題。弱引用適合于引用對(duì)象可以沒(méi)有值的情況,因?yàn)槿跻每梢詻](méi)有值,我們必須將每一個(gè)弱引用聲明為可選類(lèi)型,使用關(guān)鍵字Weak聲明為弱引用。

如下圖:Employee對(duì)Department的引用是弱引用(極端情況下員工可以沒(méi)有部門(mén))

image.png

運(yùn)行的結(jié)果:如下圖
兩個(gè)構(gòu)造函數(shù)和兩個(gè)析構(gòu)函數(shù)都走到了,說(shuō)明這兩個(gè)對(duì)象都釋放了


image.png

emp 與dept對(duì)象之前建立關(guān)系

image.png

emp 和 dept弱引用斷開(kāi)

image.png

解釋
1)emp = nil,emp被賦予空值,1#引用關(guān)系被打破,此時(shí)Employee對(duì)象的引用計(jì)數(shù)為1,即RC=1,因?yàn)檫€有4#引用,4#引用指向Employee對(duì)象,4#引用是強(qiáng)引用。所以Employee對(duì)象暫時(shí)還不能被釋放。

2)dept = nil, dept被賦予空值,2#引用關(guān)系被打破,此時(shí)Department對(duì)象的引用計(jì)數(shù)為0, 即RC = 0。

3)之所以Department對(duì)象的RC = 0,是因?yàn)闆](méi)有指向Department對(duì)象的強(qiáng)引用。3#引用是弱引用,弱引用不占用RC,RC不計(jì)數(shù),RC不對(duì)弱引用計(jì)數(shù),只對(duì)強(qiáng)引用計(jì)數(shù)。此時(shí)Department對(duì)象先被釋放。

4)Department對(duì)象被釋放之后,4#強(qiáng)引用關(guān)系被打破,即指向Employee對(duì)象的引用沒(méi)有了,緊接著Employee對(duì)象引用計(jì)數(shù)變?yōu)?,然后Employee對(duì)象被釋放。

所以運(yùn)行結(jié)果是部門(mén)對(duì)象先被釋放,然后員工對(duì)象再被釋放

無(wú)主引用

無(wú)主引用與弱引用一樣,允許循環(huán)引用中的一個(gè)對(duì)象不采用強(qiáng)引用方式引用另外一個(gè)對(duì)象,這樣就不會(huì)引用循環(huán)問(wèn)題。無(wú)主引用適用于引用對(duì)象永遠(yuǎn)有值的情況,它總是被定義為非可選類(lèi)型,使用關(guān)鍵字unowned表示這是一個(gè)無(wú)主引用。

如下圖:Department對(duì)Employee的引用是無(wú)主引用,值不能為空(部門(mén)必須有個(gè)領(lǐng)導(dǎo))


image.png

運(yùn)行的結(jié)果:如下圖
兩個(gè)構(gòu)造函數(shù)和兩個(gè)析構(gòu)函數(shù)都走到了,說(shuō)明這兩個(gè)對(duì)象都釋放了,并且員工先釋放,部門(mén)后釋放。

image.png

emp 和 dept 之間建立聯(lián)系

截屏2020-07-01 上午12.30.03.png

解釋
1)emp = nil,emp被賦予空值,1#引用關(guān)系被打破,此時(shí)Employee對(duì)象的引用計(jì)數(shù)為0,即RC=0,因?yàn)榇藭r(shí)已經(jīng)沒(méi)有指向它的強(qiáng)引用了,4#是無(wú)主引用,無(wú)主引用不占用RC,RC不計(jì)數(shù),RC不對(duì)無(wú)主引用計(jì)數(shù),只對(duì)強(qiáng)引用計(jì)數(shù)。此時(shí)Employee對(duì)象先被釋放。

2)dept = nil, dept被賦予空值,2#引用關(guān)系被打破,此時(shí)Department對(duì)象的引用計(jì)數(shù)為1, 即RC = 1, 因?yàn)榇藭r(shí)還有指向Department對(duì)象的3#強(qiáng)引用。

3)Employee對(duì)象被釋放后,3#關(guān)系被打破,即指向Department對(duì)象的引用沒(méi)有了,緊接著Department對(duì)象引用計(jì)數(shù)變?yōu)?,然后Department對(duì)象被釋放。

所以運(yùn)行結(jié)果是員工對(duì)象再先被釋放,然后部門(mén)對(duì)象再被釋放。

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

由于閉包本質(zhì)上也是引用類(lèi)型,因此也可能在閉包和上下文捕獲變量(或常量)之間出現(xiàn)引用循環(huán)問(wèn)題。
并不是所有的捕獲變量(或常量)都會(huì)發(fā)生強(qiáng)引用循環(huán)問(wèn)題,只有將閉包賦值給對(duì)象的某個(gè)屬性,并且這個(gè)閉包體使用了該對(duì)象,才會(huì)產(chǎn)生閉包強(qiáng)引用循環(huán)。

示例代碼:如下圖


image.png

運(yùn)行以上代碼:結(jié)果如下圖


image.png

沒(méi)有調(diào)用析構(gòu)函數(shù),說(shuō)明Employee對(duì)象并沒(méi)有真正被釋放。

閉包捕獲列表來(lái)解決強(qiáng)引用循環(huán)語(yǔ)法
lazy var 閉包: <閉包參數(shù)列表> -><返回值類(lèi)型> = {
    [unowned 捕獲對(duì)象] <閉包參數(shù)列表> -> <返回值類(lèi)型> in

或  [weak 捕獲對(duì)象]  <閉包參數(shù)列表> -> <返回值類(lèi)型> in

    //閉包體
}

lazy var 閉包:() -> <返回值類(lèi)型> = {
     [unowned 捕獲對(duì)象] in
  或 [weak 捕獲對(duì)象] in

    //閉包體
}

捕獲的對(duì)象有時(shí)可能為nil時(shí),則將閉包內(nèi)的捕獲聲明為弱引用
如果捕獲的對(duì)象絕對(duì)不會(huì)為nil,那么應(yīng)該采用無(wú)主引用。

代碼如下:


image.png

運(yùn)行后結(jié)果如圖:


image.png

已經(jīng)調(diào)用析構(gòu)函數(shù),說(shuō)明Employee對(duì)象已經(jīng)被釋放。

也可以用下面的無(wú)主引用實(shí)現(xiàn)


image.png

  • 如果捕獲的對(duì)象有時(shí)候可能為ni時(shí),則將閉包內(nèi)的捕獲聲明為弱引用。
  • 如果捕獲的對(duì)象絕對(duì)不會(huì)為nil,那么應(yīng)該采用無(wú)主引用。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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