- 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互相引用

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)

注:對(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))

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

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

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

解釋:
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))

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

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

解釋:
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)。
示例代碼:如下圖

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

沒(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ú)主引用。
代碼如下:

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

已經(jīng)調(diào)用析構(gòu)函數(shù),說(shuō)明Employee對(duì)象已經(jīng)被釋放。
也可以用下面的無(wú)主引用實(shí)現(xiàn)

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