Swift 記憶體管理與迴圈引用問題(weak、unowned)
之前我在CSDN上寫過一篇部落格:OC記憶體管理、ARC、property屬性、__strong、__weak(),大家有興趣的可以去看看。
今天我們來整理一下Swift的記憶體管理與迴圈引用的解決問題-weak、unowned:
記憶體管理
swift的記憶體管理也是使用的ARC(自動引用技術):當我們初始化建立一個物件例項的時候,swift就會替我們管理和分配記憶體,此時的引用計數為1,當對其進行init(copy/mutableCopy)時,引用計數會+1,而當例項被銷燬時,引用計數就會-1。當系統檢測到引用計數為0的時候,就會釋放掉這個記憶體。
但是,這種引用計數會產生一個問題就是迴圈引用:
迴圈引用
class A {
var b:B?
init() { print("A初始化") }
deinit { print("A析構掉") }
}
class B {
var a:A?
init() { print("B初始化") }
deinit { print("B析構掉") }
}
var a:A?; a = A()
var b:B?; b = B()
a!.b = b; b!.a = a
a = nil; b = nil
你會發現,A和B的解構函式deinit都沒有呼叫,因為當a執行析構的時候,b.a還在對其進行引用,當b析構的時候,a.b也在對b進行引用。這時候解決的方法就是對其中的某一個宣告進行若引用,即加上weak:
weak var b:B?
另外一種造成迴圈引用的問題就是閉包:閉包中對任何元素的引用都會被閉包自動持有,如果我們在閉包中需要使用self的話,那就相當於閉包對self持有,而block又是被self直接或間接持有,這樣就造成了迴圈引用。例如下面的程式碼:
class C{ var name:String lazy var block:()->() = { print(self.name ) } init(name:String) { self.name = name print("C初始化") } deinit { print("C析構") } } var c:C? = C(name:"c") c?.block() c = nil
這裡C的解構函式也是沒有執行的。block是self的屬性,block裡面又對self持有,這就形成了迴圈引用。所以這裡我們可以使用unowned,也可以使用weak:
//unowned
lazy var block:()->() = {[unowned self] in
print(self.name)
}
//weak
lazy var block:()->() = {[weak self] in
if let strongSelf = self{
print(strongSelf.name)
}
}
那麼這兩個使用有什麼區別呢?接下來看一個例子:
class C{
var name:String
lazy var block:()->() = {[unowned self] in
print(self.name)
}
init(name:String) {
self.name = name
print("C初始化")
}
deinit {
print("C析構")
}
}
class D{
var block:(()->())!
init(callBack:(()->())?) {
self.block = callBack!
print("D構造")
}
deinit {
print("D析構")
}
}
var c:C? = C(name:"c")
var d = D.init(callBack:c?.block)
c!.block()
c = nil
d.block()
這裡當你執行到 d.block()的時候,是會有一個error。
因為當d.block()執行的時候,c已經被析構掉了,而閉包裡的self肯定也是不存在的,是一個nil,這個時候執行的話self.name就會報錯。所以在我們不確定是否有外部變數在持有這個block的時候,我們就應該使用weak更為安全,因為使用weak的話self.name需要改成可選性的self?.name,這個時候self?.name肯定就為nil了。所以換成weak之後,在playground裡的d.block()就不會有錯誤了,而且block也是會正常執行的,只不過print(self?.name)打印出來為nil。