Swift 作為一門現(xiàn)代的高級(jí)語言,自然少不了自動(dòng)內(nèi)存管理。我們知道,JAVA 中的內(nèi)存管理是通過垃圾回收完成的。作為從 Objective-C 時(shí)代演變過來的 Swift,自然也繼承了很多 OC 的特性。OC 中的內(nèi)存管理分為兩種:一種是垃圾回收,這種內(nèi)存管理方式只被運(yùn)用于 OS X(現(xiàn)在應(yīng)該叫 macOS)中;另一種就是我們經(jīng)常提到的 ARC(自動(dòng)引用計(jì)數(shù)),這種管理方式被同時(shí)用在 iOS 和 macOS 上。在 Swift 中,內(nèi)存管理依然是通過 ARC 進(jìn)行的,下面我們就深入地看一看。
什么是 ARC?
ARC,中文名叫自動(dòng)引用計(jì)數(shù),全名叫 Automatic Reference Counting。顧名思義,意思就是“自動(dòng)的”引用計(jì)數(shù)。
在我們深入了解 ARC 之前,我們先看一看什么叫做引用計(jì)數(shù)。我們先通過一個(gè)類來看一看 Swift 中對(duì)象在內(nèi)存中的各個(gè)階段。
假如我們有一個(gè) Person 類:
class Person {
var name: String
init(name: String) {
self.name = name
print("Person \(name) has been initialized.")
}
deinit {
print("Person \(name) has been deallocated.")
}
}
這個(gè)簡單的 Person 類,我們給它指定了一個(gè) name 的屬性,同時(shí)提供了 init 構(gòu)造方法和 deinit 析構(gòu)方法。在構(gòu)造方法中,我們讓控制臺(tái)輸出一條語句,提醒我們某個(gè)對(duì)象已經(jīng)初始化。同時(shí)在析構(gòu)方法中,我們同樣讓控制臺(tái)輸出某個(gè)對(duì)象的內(nèi)存已經(jīng)釋放。
首先我們創(chuàng)建一個(gè) person1 對(duì)象,然后看看控制臺(tái)的輸出:
do {
let person1 = Person(name: "Kenneth")
}

可以看到在 do 語法塊內(nèi),我們創(chuàng)建的對(duì)象先進(jìn)行了初始化,然后運(yùn)行完畢后釋放了內(nèi)存。
Swift 中一個(gè)對(duì)象的生存周期也正如此,包含:
- 內(nèi)存分配 (從棧或者堆中獲得內(nèi)存)
- 初始化 (執(zhí)行 init 函數(shù))
- 使用 (對(duì)象的使用)
- 析構(gòu)(執(zhí)行析構(gòu)函數(shù))
- 內(nèi)存釋放 (內(nèi)存返回到?;蛘叨褍?nèi))
ARC 正是基于這個(gè)周期幫助我們管理內(nèi)存的。當(dāng)我們創(chuàng)建一個(gè)對(duì)象,保存一個(gè)對(duì)這個(gè)對(duì)象的引用,ARC 就一直監(jiān)視我們對(duì)于該對(duì)象的引用,并記下當(dāng)前有多少個(gè)對(duì)于該對(duì)象的引用。當(dāng)引用數(shù)量 >0 時(shí),內(nèi)存將不會(huì)被釋放。當(dāng)引用數(shù)量等于 0 時(shí),ARC 認(rèn)為該對(duì)象不再被需要,于是便自動(dòng)地幫助我們銷毀該對(duì)象,并釋放該對(duì)象的內(nèi)存。

ARC 失效?
在大多數(shù)情況下,ARC 工作地很好,這也讓我們不需要對(duì)內(nèi)存管理花太多心思。ARC 遵循的原則簡單而且有效,但它并不是萬能的。在 ARC 的管理下,內(nèi)存泄漏仍然可能發(fā)生。
讓我們想象這樣一個(gè)情景:我們有兩個(gè)對(duì)象,這兩個(gè)對(duì)象不再被需要,但是這兩個(gè)對(duì)象互相引用了對(duì)方。
在上述情境中我們實(shí)際上不需要這兩個(gè)對(duì)象,但是因?yàn)檫@兩個(gè)對(duì)象互相引用了對(duì)方,這兩個(gè)對(duì)象的引用計(jì)數(shù)都是 1。因?yàn)檫@兩個(gè)對(duì)象的引用計(jì)數(shù)都是 1,ARC 將永遠(yuǎn)不會(huì)釋放這兩個(gè)對(duì)象的內(nèi)存。
在這種情況下,內(nèi)存泄漏就不可避免地發(fā)生。我們把這種情況叫做引用循環(huán),更準(zhǔn)確的說,是強(qiáng)引用循環(huán)。

讓我們用一段代碼來演示這個(gè)場(chǎng)景。
首先我們新建一個(gè)簡單的 iPhone 類,這個(gè)類有兩個(gè)屬性,一個(gè)是型號(hào),一個(gè)是主人:
class iPhone {
let model: String
var owner: Person?
init(model: String) {
self.model = model
print("iPhone \(model) has benn initialized.")
}
deinit {
print("iPhone \(model) has benn deallocated")
}
}
然后我們?cè)?do 語法塊里面新建一個(gè) iPhone 對(duì)象:
do {
let person1 = Person(name: "Kenneth")
let iphone1 = iPhone(model: "7 Plus")
}
然后我們看控制臺(tái)的輸出語句,這個(gè)時(shí)候 ARC 工作正常,兩個(gè)對(duì)象都在運(yùn)行完成后被釋放了內(nèi)存:

下面我們?cè)?Person 類中新建一個(gè)屬性,這個(gè)屬性的類型是一個(gè) iPhone 類的數(shù)組,表示該人擁有的所有 iPhone。同時(shí)我們將該屬性的 setter 方法設(shè)為私有,這樣強(qiáng)制了使用我們自定義的方法進(jìn)行 set。
private(set) var iphones: [iPhone] = [iPhone]()
func add(iphone: iPhone) {
iphones.append(iphone)
iphone.owner = self
}
然后我們?cè)?do 語法塊中添加一條語句:
do {
let person1 = Person(name: "Kenneth")
let iphone1 = iPhone(model: "7 Plus")
person1.add(iphone: iphone1)
}
這時(shí)候我們?cè)倏纯刂婆_(tái):

可以看到這個(gè)時(shí)候的控制臺(tái)少輸出了兩天釋放內(nèi)存的提示。這說明我們剛才的這兩個(gè)對(duì)象并沒有被釋放。
為什么?
就像我們上面所說的一樣,這個(gè)時(shí)候發(fā)生了強(qiáng)引用循環(huán)。我們的 person1 對(duì)象中的 iphones 屬性保有了 iphone1 對(duì)象,而我們 iphone1 中的 owner 屬性又保有了 person1。這個(gè)就是我們所說的強(qiáng)引用循環(huán)。
破解之道?
弱引用(Weak Reference)
為了打破所謂的強(qiáng)引用循環(huán),我們開始思考,有沒有一種辦法能夠讓我們同時(shí)可以進(jìn)行引用但是又不增加引用計(jì)數(shù)呢?弱引用就是這樣誕生的。
所謂弱引用,就是很弱的引用——它引用一個(gè)對(duì)象而不增加該對(duì)象的引用計(jì)數(shù)。

上圖中,虛線箭頭表示一個(gè)弱引用。可以看到對(duì)象 2 弱引用對(duì)象 1 時(shí),對(duì)象 1 的引用計(jì)數(shù)并沒有增加。
當(dāng)變量 1 和變量 2 不存在后,對(duì)象 1 的引用計(jì)數(shù)變成 0,對(duì)象 1 會(huì)被 ARC 銷毀,銷毀后對(duì)象 1 對(duì)對(duì)象 2 的引用也不存在,對(duì)象 2 也會(huì)被 ARC 銷毀。強(qiáng)引用循環(huán)就這樣被打破了。
針對(duì)上一段的代碼,我們只要在 iPhone 類的 owner 屬性前加一個(gè) weak,即可以把該引用改為弱引用:
weak var owner: Person?
這時(shí)候我們?cè)倏纯刂婆_(tái):

可以看到這個(gè)時(shí)候兩個(gè)對(duì)象的內(nèi)存都會(huì)被正常釋放,弱引用起作用了。
你可能會(huì)問,那我們保存的弱引用這個(gè)時(shí)候指向了什么呢?我們回過頭來看一看 owner 屬性的類型,實(shí)際上是一個(gè) Optional。聰明的你肯定能猜到,弱引用指向的對(duì)象如果內(nèi)存被釋放,會(huì)自動(dòng)變?yōu)榭找茫簿褪?nil。
無主引用(Unowned Reference)
弱引用 weak 作為解決強(qiáng)引用循環(huán)的一個(gè)關(guān)鍵字,它將會(huì)在被引用對(duì)象的內(nèi)存釋放后變?yōu)?nil。Swift 中還提供了另外一個(gè)關(guān)鍵字——unowned,它也能達(dá)到引用對(duì)象而不增加其引用計(jì)數(shù)的效果。和 weak 關(guān)鍵字不同的是,weak 關(guān)鍵字會(huì)在對(duì)象內(nèi)存釋放后變?yōu)?nil,而 unowned 會(huì)繼續(xù)保有對(duì)該對(duì)象的引用,即使該引用已經(jīng)變?yōu)闊o效。
在上一段,我們知道 weak 關(guān)鍵字描述的屬性必須申明為可變變量并且是 Optional 類型,這是因?yàn)樗赡軙?huì)變成 nil。
unowned 關(guān)鍵字則必須不是 Optional 類型,也就是說我們?cè)谠L問 unowned 變量時(shí),不需要進(jìn)行對(duì)于 Optional 類型的解包操作,可以直接訪問。然而這也造成了如果我們的 unowned 變量所指向的對(duì)象內(nèi)存被釋放后,我們?cè)僭L問這個(gè)變量會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤。
三種引用可使用的情景:
| | var | let | Optional | 非 Optional |
| ------------- |:-------------:|
| 強(qiáng)引用(Strong) | ?? | ??|??|??|
| 弱引用(Weak) | ?? | ?|??|?|
| 無主引用(Unowned) | ?? | ??|?|??|
我們用一段代碼來演示 unowned 關(guān)鍵字使用的情景。假設(shè)我們現(xiàn)在有兩個(gè)類,一個(gè) Customer 類,和一個(gè) CreditCard 類。我們知道,一張信用卡一定有一個(gè)主人,而一個(gè)消費(fèi)者不一定有一張信用卡。因而我們需要在 Customer 類設(shè)計(jì)一個(gè) Optional 的信用卡屬性,然后在 CreditCard 類設(shè)計(jì)一個(gè)非 Optional 的消費(fèi)者類。同時(shí)我們?yōu)榱吮苊鈴?qiáng)引用循環(huán)的產(chǎn)生,我們把 CreditCard 類中的消費(fèi)者屬性設(shè)計(jì)為 unowned。
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
然后我們創(chuàng)建兩個(gè)對(duì)象,看一看控制臺(tái)是否正常輸出:
do {
let cus1 = Customer(name: "Kenneth")
let cus2 = CreditCard(number: 6222136579841254, customer: cus1)
}

在這種情況下,CreditCard 類和 Person 類相互引用了對(duì)方,但是因?yàn)槲覀儼?CreditCard 類中的 customer 屬性設(shè)計(jì)成 unowned,就不會(huì)出現(xiàn)強(qiáng)引用循環(huán)。
閉包中的循環(huán)引用
TBD...