[iOS 理解] weak

前面內存管理的文章寫了其他的修飾符,__weak 單獨在本文寫
除了 __weak 之前,還有一些修飾符與內存管理有關:
__block 要單開一篇文章;
Core Foundation 對象 與 OC 對象間的強制轉換與內存管理問題先介紹一下(ARC)。

強制轉換

以 CFArrayRef 爲例

__bridge_retained
{
	id obj = [NSArray new]; // obj 引用計數爲 1	
	CFArrayRef ref = (__bridge_retained CFArrayRef)obj; // obj 引用計數爲 2
	CFRelease(ref);
}
ref 和 obj 共同維護引用計數 2,就像是 retain 一樣,各自還需要 release:
手動釋放 ref 的引用,CFRelease 
obj 出作用域時,ARC 有 release 
__bridge 1
{
	id obj = [NSArray new]; // obj 引用計數爲 1	
	CFArrayRef ref = (__bridge CFArrayRef)obj; // 引用計數仍爲 1
}
__bridge 直接轉換爲 C 指針時不會增加引用計數,仍爲 1
因爲 ARC obj 會自動釋放一次,所以不可以再 CFRelease(ref)
__bridge_transfer
{
	CFArrayRef ref = CFArrayCreate(kCFAllocatorDefault, 0, 0, 0); // ref 引用計數爲 1	
	id obj = (__bridge_transfer NSArray *)ref; 	// 引用計數仍爲 1
}
只要是轉換成 id,一定會 retain 一次,引用計數一定會 +1。
但是 __bridge_transfer 的意圖是主權轉移,既然是轉移,那麼轉移前後引用計數不能變
爲了滿足修飾符的意圖,編譯器會插手,幫忙調用一次 CFRelease(ref)

整個過程結束,引用計數不變,就像管理權轉移給了 OC 對象一樣,“transfer”
__bridge 2
{
		CFArrayRef ref = CFArrayCreate(kCFAllocatorDefault, 0, 0, 0); // ref 引用計數爲 1	
		id obj = (__bridge id)ref; 	// 引用計數爲 2
		// 轉換成 id,obj 直接 retain,引用計數 +1,變爲 2
		CFRelease(ref);
		// ARC obj 自動 release,還需手動 CFRelease
} 

熟悉C/C++的話,得到 CF 指針 和 得到 void* 沒區別,太靈活了,可以爲所欲爲

__weak

只要理清思路,看代碼非常簡單

需求

爲了解決循環引用:A ——> B, B ——> A
想達到一種效果:A ——> B, B - - - -> A,即 B 不增加 A 的引用計數,這樣 A 不依賴 B 就可以被釋放。
同時,A 釋放過程到最後,會把 B 的 A 指針設爲 nil,防止訪問野指針。

存儲結構

一個對象釋放後,要把所有弱引用自己的指針設爲 nil,則肯定需要一個子結構來保存所有的弱引用者,而且保存的是弱引用者的地址,才能在釋放時根據地址修改值爲 nil。

因爲哈希表插入刪除速度logn,所以肯定用哈希表
整體的大致結構應該是 外層哈希表用來索引被引用者
子哈希表用來索引 被引用者的所有弱引用者集合

因爲 weak 使用量實際並不多,所以兩層哈希表都是經典線性開地址的
(引用計數表用了 DenseMap,是空間換時間,引用計數表必須得極致速度)
Java 中哈希表的思路是,如果集合中元素數小於7(或其他數),就用鏈表,大於7就轉換爲紅黑樹,操作過程中元素數小於5了就轉換爲鏈表。(元素特別多時,紅黑樹讀寫性能非常好)

蘋果在子哈希表上,大方向上用了這個思路:如果元素數小於等於4,用數組,大於4轉換爲哈希表。

被引用對象稱 referent,弱引用的變量稱 referrer
一個map,referent --> (referent 及其子map),子map 是 referrer *

核心流程

1 假設初始化一個 referrer,需要操作表:在表中尋找入口 referent。如果找到了,得到了子map,插入map;如果沒找到,則先創建入口,再插入到map。
2 假設一個 referrer 要離開作用域或主動更新爲nil,需要操作表:在表中尋找 referent。如果找到了,得到子map,從子map中刪除該 referrer* 項,如果map沒有剩下項了,從父表刪除入口。
3 假設一個 referrer 要改變引用對象,則先撤銷引用舊對象,即2,然後1
4 referent 正在被釋放時,如果 isa 上標記了有被弱引用過,則去父表找入口,如果找到子表,全部 referrer 設爲 nil,刪除入口。
5 讀取 referrer 的值,會 retain 並 autorelease,以保證讀取後可以一直用。

剩下情況,利用核心邏輯排列組合就行了

源碼細節

我寫的註釋,都加了 // el_comment
代碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章