天天看點

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。

歡迎大家通路我的github:https://github.com/FCF5646448,如果有能幫助到大家的地方,可以鼓勵我個小星星哦