Swift中是採用自動引用計數(ARC,AutomaticReferenceCounting)機制來對內存進行管理的。
一、簡述ARC如何工作:
每當你每創建一個新的對象,它便會分配一塊內存來存儲該對象的相關信息。當你不再需要這個對象的時候,它就會自動釋放這個對象,避免它再佔用內存空間。當然,如果該對象只要至少存在一個引用就不會被釋放。另外,你如果訪問了一個已經被釋放的對象,則很有可能會出現崩潰(野指針)。
拿《The Swift Programming Language》裏面的例子來套一下:
//創建一個Cat類,自帶常量name屬性
class Cat {
let name: String
init(name: String) {
//初始化name屬性
self.name = name;
print("\(name) 對象已經被初始化")
}
deinit {
print("\(name) 對象釋放成功!")
}
}
接下來定義三個變量類型爲:Cat?用來對一個新的Cat對象建立多個引用。因爲變量是一個可選類型(是Cat?而不是Cat), 他們會自動給變量初始化爲nil,所有當前它不需要引用一個Cat對象。
var cat1: Cat?
var cat2: Cat?
var cat3: Cat?
創建一個新的Cat對象,並對之前定義的三個變量進行賦值操作:
cat1 = Cat(name: "jack")
//輸出:jack 對象已經被初始化
當你調用Cat類構造器,它會輸出:”jack 對象已經被初始化”。這就代表已經完成了對象初始化操作。接下來,一個新的Cat對象被賦值給了 cat1變量,從而建立了cat1對新Cat實例對象的強引用。因此,它存在了至少一個強引用。這個新的Cat對象就不會被釋放。
如果你把同一個Cat實例對象賦值給另外兩個變量,它則又多建立了兩個強引用。
//通過賦值對Cat實例對象建立強引用
cat2 = cat1
cat3 = cat1
此時這個Cat實例對象已經有三個強引用了。
如果你用把其中兩個變量賦值爲nil的方式來打破這些強引用,對象是不會被釋放的。因爲它還存在一個強引用。
//通過把置nil來打破cat1,cat2對Cat實例對象的強引用
cat1 = nil
cat2 = nil
只有直到對象所有的引用被打破,ARC纔會釋放該對象
//打破最後一個強引用
cat3 = nil
//輸出:jack 對象釋放成功!
當最後一個強引用被打破後,這個Cat對象被成功釋放,輸出:jack 對象釋放成功!
二、循環引用
1、什麼是循環引用?
簡單的來說就是兩個對象互相持有對方,使對方處於活躍狀態不被釋放,從而導致了循環引用。套個例子:
//創建一個Cat類,擁有一個String類型的name屬性與一個可選類型的dog屬性。
class Cat {
let name: String
init(name: String) {
self.name = name;
print("\(name) 對象已經被初始化")
}
var dog: Dog?
deinit {
print("\(name) 對象釋放成功!")
}
}
//創建一個Dog類,擁有一個String類型的type屬性與一個可選類型的jack屬性。
class Dog {
let type: String
init(type: String) {
self.type = type
print("\(type) 對象已經被初始化")
}
var cat: Cat?
deinit {
print("\(type) 對象釋放成功")
}
}
定義兩個變量並初始化
var jack: Cat?
var tom: Dog?
jack = Cat(name: "jack")
//print:jack 對象已經被初始化
tom = Dog(type: "red")
//print:red 對象已經被初始化
建立強引用
jack!.dog = tom
tom!.cat = jack
通過把對象置nil打破強引用
jack = nil
tom = nil
這個地方Cat對象和Dog對象都不會被釋放,因爲他們兩個之間還存在強引用。
解決循環引用辦法
Swift提供了兩種方法來解決循環引用問題:(1)弱引用(weak)
(2)無主引用(unowned)
聲明變量或屬性時,在前面加上”weak”關鍵詞表示它是一個弱引用。如果確定訪問屬性或函數的時候該對象不會被釋放,也可以用無主引用(unowned)來取代。unowned跟Objective-C中的unsafe_unretained類似。它與weak的區別在於weak在引用的對象被釋放後,會自動置nil。所以它必須聲明成可選類型(?)。而unowned在引用對象被釋放後不會自動置nil(因爲非可選類型的變量不能被設置成nil),而是保留對原有對象的無效引用,一旦對象被釋放,再訪問該對象的屬性或函數就會造成崩潰。官方建議是當你在可以確定訪問屬性或函數所屬對象不會被釋放時,使用unowned。否則,還是使用weak吧!
解決上面代碼的循環引用問題,可做如下修改:
class Dog {
let type: String
init(type: String) {
self.type = type
print("\(type) 對象已經被初始化")
}
weak var jack: Cat
deinit {
print("\(type) 對象釋放成功")
}
}
jack = nil
tom = nil
2、常見的循環引用問題:
(1)delegate
在項目中我們經常會用到代理模式,在Objective-C中我們一般在delegate前面加上weak關鍵詞,表示它是一個弱引用。從而達到打破循環引用的效果。
//在前面加上@objc是指定這裏的代碼屬於Objective-C的代碼,因爲Swift中協議可以用於class、struct、enum。對於基本數據類型,它不是對象,沒有引用計數這個概念,因此無法使用weak關鍵詞。 Objective-C中的協議只適用於類,所以這裏需要加上@objc。
@objc protocol AClassDelegate {
func wash()
}
class A {
weak var delegate: AClassDelegate!
func cry() {
}
func play() {
delegate.wash()
}
}
class B: AClassDelegate {
func feed() {
let nurse = A()
nurse.delegate = self
nurse.cry()
}
@objc func wash() {
print("begin wash and sleep")
}
}
(2)Block(閉包)中的循環引用
Block(閉包)的循環引用在於它會持有所有引用的元素。比如我們在Block(閉包)中訪問了self,那麼它就持有了self。這樣一來就形成了一個環:self持有Block(閉包),Block(閉包)持有self。下面我們舉個栗子加以說明:類A中有個閉包,我們在閉包中訪問了self的name屬性。
class A {
let name: String
lazy var wash: Void ->Void = {
var name = self.name + " nurse"
print("wash room is \(name)")
}
init(name: String) {
self.name = name
print("\(self.name) 創建了")
}
deinit {
print("\(self.name) 釋放了")
}
}
class B {
func play() {
var nurse: A?
nurse = A(name: "jack")
nurse?.wash()
}
}
var baby: B?
baby = B()
baby?.play()
輸出結果:
jack 創建了
wash room is jack nurse
因爲wash是self的一個屬性,所以wash被self持有。這個時候我們又在閉包中訪問了self的name屬性,所以它又在閉包中持有了self。這樣便導致了循環引用。爲了解決閉包中的循環引用問題,Swift中提供了一個叫”閉包捕獲列表”的解決方案。
定義一個捕獲列表
捕獲列表中的每一項都是一個用weak或 unowned關鍵字與一個實例對象的引用(如self)或者是一個用初始化值的變量(如delegate = self.delegate!)搭配成對,配對之間用逗號隔開,最後用方括號括起來。如下所示:
lazy var someClosure:(Int, String) -> String = { [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in }
如果閉包中沒有指定參數列表或者返回類型,可直接把捕獲列表放置在閉包開始的地方(花括號後面)然後緊跟着是”in”關鍵詞,示例代碼如下:
lazy var someClosure:Void -> String = { [unowned self, weak delegate = self.delegate!] in }
如果捕獲的引用總是不會爲nil,那麼它應該作爲一個無主引用(unowned)被捕獲,這樣會比弱引用(weak)要好。
解決Block(閉包)中的循環引用,ClassA中的Block(閉包)代碼可做以下修改:
lazy var wash:Void ->Void = { [weak self] in
//這個地方使用self!.name而不是self.name。是因爲weak修飾,self有可能爲nil,如果是unowned修飾,這個地方應該是self.name。因爲unowned修飾的self永遠不可能爲空。
var name = self!.name + " nurse"
print("wash room is \(name)")
}
輸出結果:
jack 創建了
wash room is jack nurse
jack 釋放了