天天看點

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

這還得從使用aspects這個庫說起,如下圖:

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

上圖中的id token 的類型如下圖:

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

aspectidentifier 的block屬性對應的是圖一的傳參usingblock,很明顯token引用了usingblock,usingblock引用了token,構成循環引用,這個是一個正常的循環引用記憶體洩漏。可是fbretaincycledetector沒有掃描出環,一開始我懷疑是我自己改造過的allocationtracker沒有跟蹤到aspectidentifier執行個體(閑魚改造了fballocationtracker),經排查allocationtracker是有記錄的,可是為什麼沒有掃描出環呢?

1.記錄下運作過程中建立和釋放的執行個體

2.分析未釋放的執行個體retain了哪些執行個體(對應深度優先周遊的臨接表),逐層展開,形成一個有向圖

3.使用深度優先周遊圖的過程中,檢視是否有成環

一般的oc對象擷取retain的執行個體通過運作時接口很容易就可以擷取到,block就比較特殊,下面介紹一下fbretaincycledetector 如何擷取block捕獲的變量,并确定哪些變量是被block強引用的:

對應的接口 static nsindexset _getblockstronglayout(void block)

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

判斷block是否強引用其他變量并拷貝至堆上:

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

block引用了 1) c++ 棧上對象 2)oc對象 3) 其他block對象 4) __block修飾的變量,并被拷貝至堆上時則需要copy/dispose輔助函數,輔助函數由編譯器生成

除了case 1,其他三種case都會分别調用下面的函數:

void _block_object_assign(void destaddr, const void object, const int flags);

void _block_object_dispose(const void *object, const int flags);

_block_object_assign(或者_block_object_dispose)會根據flags的值來決定調用相應類型的copy helper(或者dispose helper)

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑
FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑
FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑
FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑
FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

現在回歸最開始那個問題,從圖一可以知道usingblock捕獲了3個變量,

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

隻有disposal、guidehandler,沒有__block id token

對比一下三者的差別,隻有token是__block聲明過(這裡已經确定token沒有被釋放,檢視allocationtracker記錄是否有token變量即可),說明fb擷取block捕獲變量的方案對__block無效

這是我聲明的一個block:

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

對其執行clang -rewrite-objc編譯轉換成c++實作

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑
FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

從上圖可以确定block捕獲的變量就存放在block的abi struct 的末尾;__block聲明的變量要複雜一些,isa、forwarding、flags、size是必有,disposer、copy是obj-c對象才有

來看看block的disposer和copy函數實作:

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

分别對捕獲的變量做了相應的disposer和copy處理,第二個參數傳人的值是不一樣的,估計就是這個參數有關聯

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

看一下紅色框:這裡直接跳過了block的頭部,做了一個小優化,直接從存放變量的索引開始

1.是否是malloc指針:不做這步過濾,會導緻指針的強轉取值crash,因為block可能捕獲了一個inter資料,這時如果當成指針來處理,那麼基本上會被當作通路非法指針而崩潰

2.去掉__block變量沒有disposer和copy 函數的變量,這個使用size大小來判斷即可

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑
FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑
FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

上面每一個構造指派都是必不可少的

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

diposer() 釋放了 struct blockbyref 下的void *refobj對象(即fbblockstrongrelationdetector對象)

2.這裡注意一下,為什麼沒有對malloc出來的的__block變量進行free?這裡調用free的話,會導緻crash,因為block的disposer函數調用到了__block變量自己的disposer接口的時候,已經釋放了這段記憶體,我是通過打開malloc scribble 這個調試開關,發現這段記憶體有部分被置為了0x55确定的。

FBRetainCycleDetector不能掃描__block變量的問題分析和解決方案問題描述FBRetainCycleDetector的原理簡介回歸問題解決方案git路徑

從圖檔可以看出來__block變量已然被解析出來了

我把修改送出到fbretaincycledetector的github上的一個個人分支,等待fb的省核,附上git位址:

<a href="https://github.com/chaokongzwp/fbretaincycledetector.git">https://github.com/chaokongzwp/fbretaincycledetector.git</a>

繼續閱讀