1. 程式人生 > >Swift 記憶體管理與迴圈引用問題(weak、unowned)

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。