Swift進階-內存管理


本文的主要目的是探索 RefCount 的內存結構及強/弱引用計數管理

 

Swift 中也是采用 ARC 編譯器自動內存管理機制。

Swift 對象的內存結構是 HeapObject, 有兩個屬性 Metadata 和 RefCount , 各占8字節(64位)

RefCount 的每位的數據存儲內容如下圖所示:

 

1. 強引用的引用計數

1.1 數據結構

 

數據結構體大概是這樣:

struct InlineRefCountBits {
    var strongref: UInt32
    var unownedRef: UInt32
}

 

1.2 存儲方式

源碼中定義了 64 位的 refCount 聯合體的不同位置,存儲的不同含義:

 

 

我們通過下面這個小案例,來看看強引用的引用計數的存儲方式:

 

我們把這個值放到計算器中,對比着看下 64 位的二進制存儲情況:

 

33 - 62位存儲的是強引用數量,二進制的 11, 表示的是3,也就是有3個強引用計數強引用計數是從0開始的,t, t1, t2 三個變量指向創建的對象,所以對象的強引用計數為3。

那控制台里打印的0x0000000600000002中的6是怎么回事呢?

  • 由於我們在控制台打印的是16進制,每4位為一組,第32 - 36位的 0110, 表示的是數字6,所以才會顯示成0x0000000600000002,這里只是進制計算方式不同,並不是代表有6個引用計數。 
  • 由於二進制是從第33位開始存儲的值是11,16進制卻從32位開始存儲的值是110110 比 11向左移1位,所以就形成了2倍關系。所以我們從控制台打印出來的值,除以2,就是真實的強引用計數了

1.3 作用

ARC下,編譯器會根據強引用數量,來判斷一個對象是否應該被銷毀。如果強引用為0,就會銷毀這個對象

如果兩個對象,互相強持有對方,則會造成引用計數均不能為0,也就是無法銷毀,從而造成內存泄露的問題。

這時就需要弱引用或者無主引用,來將一個持有方式改為非強引用。這樣就可以解除循環引用的問題。

 

下面就是一個循環引用的例子:

class Teacher{
    var age = 18
    var closure:(()->())?
    
    deinit {
        print("Teacher 銷毀了")
    }
}

func test() {
    var t = Teacher()
    t.closure = {
        t.age = 10
    }
    print("方法執行完了")
}

test()

 

 

由於對象 t 持有closureclosure 內又捕獲了 t 的變量,從而強持有了 t . 所以造成了循環引用。

2.弱引用

使用weak 來修飾變量,這個變量就是弱引用,強引用計數不會再加1。weak 修飾后的變量,會變成一個可選項,也就是可以將 nil 賦值給它。

2.1 數據結構

數據結構:

 

調用流程:

 

所以WeakReference大概是這樣的結構:

struct WeakReference {
    var entry: HeapObjectSideTableEntry
}

struct HeapObjectSideTableEntry {
    var object: HeapObject
    var refCounts: SideTableRefCounts
}

struct SideTableRefCounts {
    var strongref: UInt32
    var unownedRef: UInt32
    var weakBits: UInt32
}

struct HeapObject {
    var kind: UnsafeRawPointer
    var strongref: UInt32
    var unownedRef: UInt32
}

 

2.2 存儲方式

 

下面是個例子:

 

 

我們創建了4個弱引用的變量,弱引用計數從1開始計數,所以是5. 

2.3 作用

weak 修飾的類型會自動變為可選類型,可以用於解除循環引用,在對象銷毀后,編譯器會自動置為nil。

1.3 中的例子,在捕獲 t 的時候,使用 weak 修飾一下,就不會形成閉包對 t 對象的強引用。

func test() {
    var t = Teacher()
    t.closure = {[weak t] in
        t?.age = 10
    }
    print("方法執行完了")
}

 

 

3 無主引用

存儲方式及數據結構在強引用中,已經分析過了,下面這個圖是結果:

 

3.1 計數方式

 

所以無主引用的計算方式為: 

  1. 轉換成 2 進制, 比如 6 轉成 110
  2. 右移一位,110 變 10, 也就等於10進制的3
  1. 減 1,3 - 1 = 2

對象 t 的無主引用的值是 2

 

3.2 作用

由於 weak 修飾后的變量是可選項,使用時需要解包,比較麻煩。 

如果能從上下文中確定變量的一定有值的話,可以使用 unowned 修飾變量來解除循環引用,因為它也不是強引用。這有點類似於隱式解包,在確定有值時再使用,如果對象釋放后,再執行這個閉包,會崩潰的。

 


4. 獲取引用計數

4.1 CFGetRetainCount

需要注意的是, 如果我們使用CFGetRetainCount來獲取強引用計數,這個方法的內部,會自動將當前的引用計數加1. 這一邏輯與 OC 是一致的:

4.2 swift_retainCount

從源碼看,swift_retainCount 獲取引用計數的時候,也是默認 + 1 的。

 

 

5.引用計數加1

在ARC下,編譯器為我們自動做了內存管理的操作,在有強引用 / 弱引用 / 無主引用指向對象時,都會執行對應的方法,去將對象對應的引用計數 + 1。

 

下面是swift_retain(強引用)的源碼:

 

6.引用計數減1

在ARC下,編譯器為我們自動做了內存管理的操作,當指向對象的變量離開當前作用域時,根據對應變量的類型(強引用 / 弱引用 / 無主引用),執行對於的方法,將對象對應的引用計數 - 1。

 

下面是swift_release(強引用)的源碼:

 

好了 給小伙伴分解到這里就到此為止吧,以后會慢慢給大家講解說明。

青山不改,綠水長流,后會有期,感謝每一位佳人的支持!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM