上一節,介紹了方法調度 & @objc & 指針。本節,我們就探究較難的引用計數
,將從以下4個方面探索:
- Swift三大引用計數(strong、unowned、weak)
- 強引用 & 無主引用
- CFGetRetainCount計數統計
- 弱引用
swift
中的引用計數
與OC一致
,都是采用ARC
(自動引用計數)管理。
- OC引用計數:(可參考OC內存管理 -> 3.引用計數)
OC的對象都是以
objc_object
為模板創建,其中首元素
是isa
:
開啟
指針優化(nonpointer): 在isa
中存儲引用計數
,可使用散列表
進行拓展
存儲未開啟
指針優化: 直接使用散列表
進行存儲
。
- swift引用計數:
swift對象
都是以HeapObject
為模板創建,其中HeapObject
的模板中第二個元素
,是refCount
引用計數屬性,該屬性記錄了strong
(強引用計數)和unowned
(弱引用計數)等信息。weak修飾
的對象
,會另外生成WeakReference
對象,內部HeapObjectSideTableEntry
散列表類在原heapObject類
的基礎上,重新記錄了refCount
(管理strong
和unowned
引用計數)并新增了weakBits
弱引用計數。
1. Swift三大引用計數(strong、unowned、weak)
首先,我們先通過案例,體驗一下Swift
對象的三種引用類型:
-
strong
(默認強引用類型)、unowned
(無主引用類型)、weak
(弱引用類型)
image.png
- 不管是哪種
引用
,持有
的都是原對象
(從p到p5內存地址可以看出)- 在每一行執行完后,
x/4g
打印p
對象內存信息
,在第二
個地址
上,可以清晰感受到,強引用
和無主引用
的引用計數
在有規律
的增加
,而弱引用
卻沒有變化
。
- 經過了上面的初體驗,我們對
強引用
和無主引用
計數的位置
有了初步的感受,但弱引用
的信息存放不明朗
。
- 下面,我們通過
案例
、SIL中間代碼
、Swift源碼
、匯編
等方式,一點點揭開
他們的面紗
??
2. 強引用 & 無主引用
2.1 源碼探索
- 當前以
默認
的initialized
方式進行初始化
,分析HeapObject對象
的引用計數
swift源碼探索
過程:
image.png
-
refCount
的內存布局
:
image.png
- 現在,我們知道
強引用
和無主引用
是在Uint64_t
8位
的refCount
的不同位置。
2.2 引用計數分析
下面通過案例
來檢查
一下:
- 創建一個
Swift
的命令行項目
:
class HTPerson {
var age = 10
var name = "ht"
}
var t = HTPerson()
var t1 = t
var t2 = t
print("end")
- 【嘗試一】: 在
t1處
打斷點
。t對象
的強引用
和無主引用
的計數
都為1
image.png
- 【嘗試二】: 在
t2處
打斷點
。t對象
的強引用計數
為2
和無主引用計數
為1
image.png
2.3 強引用計數+1
- 還是以上面
測試代碼
為例,我們結合SIL
中間代碼和Swift源碼
分析:
【情況一】僅
創建對象
,默認強引用計數
為1
image.png
【情況二】進行
一次引用
,強引用計數
為2
,SIL
中可以看到copy_addr
,匯編
可以看到使用swift_retain
,在swift源碼
中可以知道執行路徑為:
swift_retain
->refCounts.increment(1)
->incrementStrongExtraRefCount
->強引用計數+1
image.png
3. CFGetRetainCount計數統計
-
CFGetRetainCount
會在執行前
,對對象
進行strong_retain
操作,在執行后
,完成release_value
操作。
所以swift
中CFGetRetainCount
打印的強引用計數
,會
比原引用計數多1
。
注意:swift中
,在lldb
中p打印
內存,會引用計數+1
,影響影響CFGetRetainCount
的結果
(斷點,p打印一次或多次,x/4g
在內存信息中可看到引用計數
明顯變化
)
【情況一】
不打印
,無retain和releaseimage.png【情況二】
打印
一次CFGetRetainCount
,執行前strong_retain
+1,執行完release_value
-1image.png
4. 弱引用
- 我們知道
swift
是使用ARC
(自動引用計數管理)的。如果產生循環引用
,我們必須有弱引用
機制去打破循環
。
swift中的
弱引用
,使用weak修飾
。與OC不同的是:
OC
:
弱引用計數
是存放在全局維護
的散列表
中,isa
中會記錄
是否使用了散列表
。
在引用計數
為0
時,自動觸發dealloc
,會檢查
并清空
當前對象
的散列表計數
。
swift
:
弱引用計數
也是存放在散列表
中,但這個散列表
不是全局的。
- 如果對象
沒有
使用weak
弱引用,就是單純的HeapObject
對象,沒有散列表
。- 如果使用
weak
弱引用,會變為WeakReference
對象。這是一個Optionl(可空對象)
。其結構中自帶散列表計數
區域。
但swift
的散列表
與refCount
無關聯。當強引用計數
為0
時,不會觸發散列表
的清空。而是在下次訪問
發現當前對象不存在(為nil)
時,會清空散列表計數
。
下面,我們通過案例
和源碼
來分析swift
的弱引用
: WeakReference對象
和內存結構
案例:
可以發現:
weak修飾前
,p對象是HeapObject類型
,可從refCount
中看出強引用計數
和無主引用計數
。
weak修飾后
,p對象的類型變了
image.png可以看到
weak修飾
的p1對象
,變成了optinal可選值
。
(不難理解,weak修飾
的對象
,不
會改變
原對象的引用計數
,只是多
一層可空
的狀態
)
image.png
斷點
,匯編
可以看到swift_weakInit
初始化,swift_weakDestroy
釋放。
image.png進入
swift源碼
,搜索swift_weakInit
:image.png
常規對象
與弱引用對象
區別:image.png
- 現在,我們已知道
弱引用
實際上是WeakReference
對象,信息
都存儲在side
弱引用表中,可仿照getSideTable
函數左移3位
得到side散列表地址
。讀取弱引用信息
:我們回到
上面案例
:
image.png
- 了解結構后,關于
弱引用
的引用計數+1
、-1
、釋放
都在WeakReference
類中有介紹,可以自行了解。