說到檢測項目中的循環引用 能夠有不少手段,其中牛叉的 instruments 固然是把利器。html
固然開發過程當中每每會大意引發的 循環引用git
好比:忘寫了 @weakify(self) && @strongify(self); 在大量使用RAC 和 block.....github
固然引發這類緣由還有不少...app
若是分工明確的話可能會再項目結束後,專門測試這塊...然而好像並非每次迭代都會作這塊的工做,除非被明確發現引發崩潰的狀況。iview
so 要是能把這個工做引入debug 期間,若是引發循環引用 能夠拋出異常給開發人員,及時修復 工具
這方面仍是有些大牛寫的工具的:如 FLEX 測試
還有 HeapInspector-for-iOS 能夠去看看ui
但有個缺陷時都須要開發人員本身進行分析(),不然仍是沒法暴露這類問題。spa
so 仍是本身動手吧!debug
切入點在 UINavController 和 UIViewController ,
app 最多的仍是界面展現上,可能會對ViewController 頻繁操做,如 push 一個VC、 present 一個VC
最理想的狀況下(大部分狀況下)你把vc 推出 nav棧 也就意味這系統要回收vc,固然要用到Nav 。
VC 中又會包含不少其它成員,最終直接獲取間接的被 VC 所引用。若是VC 或 VC中的成員 遇到強引用循環,那麼VC被推出NAV棧後
就不會被釋放,如能拋出異常,進行分析的話就很好解決啦。
固然也會有例外的狀況:
假如 VC1 有個@p(n,strong)VC2 的成員屬性。而後再 VC1中 使用nav 推出 VC2. VC2被推出nav棧並不會釋放。
可是 若是VC1也能夠被推出Nav棧的話 一定會被釋放,若是不是rootVC的話(除非它又被 另外一個VC強引用着,這也太奇葩啦。。)
正常狀況很好解決,在vc 被推出nav 棧後開一個計時器 2s內若是vc 沒有被釋放,則拋出一個異常。
這裏會有一個狀況:
vc 確實被釋放啦,當在2s 後才釋放,也就時延時啦,(2s 內正常狀況下vc 都會釋放的)。引發這種緣由不少,
vc 被一些"複雜的引用關係" 直接或間接的引用啦,形成vc 釋放時等着什麼東西釋放。
好比使用
vc 中使用[self performSelector:@selector(xxx) withObject:nil afterDelay:10]; 我在進入vc 4s後就推出,
那麼vc 會等着這個10s的任務完成後纔會被釋放。試想我vc 都被推出去拉(看不到啦),在去執行一個任務有多大意思
除非是一些必要的操做,若是是關於ui的操做就更不該該啦。
這種狀況固然應該避免,程序中保持簡單高效的引用是頗有必要的。尤爲內存方面。
代碼很簡單,對於那個例外(vc 被強引用啦),開發人員本意就是要讓vc 被強引用,使用了上面邏輯的代碼每次都會死在某些地方。
我還忽略不了,必須重啓。又死在同一個地方...。估計會被罵死。。
so 。若是能夠忽略掉那個異常,是否是就方便多拉。(斷言,拋出異常、其它機制都會使程序掛掉)若是能用代碼打個斷點,豈不妙哉。
最後來看看那個例外。我怎麼知道vc 在被推入nav 棧時被強引用啦??
最初至關 retainCount . 你們都知道 這方法不靠譜,徹底沒有什麼意義。
期間我去重寫 NSObject 的 release retain 一些內存管理的方法,試圖本身記錄retainCount.
然而結果並非想象中那樣,一個徹底沒有被強引用的VC 初始化出來,而後被加入到nav 棧以前,我記錄的retainCount 已是5仍是6來着。。
確實困擾了一兩天,有時候長時間想不出的問題,可能已經在牛角尖啦0.0
如何變通,若是能肯定vc 是某一個VC的成員是否是就能夠解決啦。
so 變成 給定實例 A 和 B 如何肯定實例B 是 實例A的成員。
能夠根據A 去Class 裏找ivar 列表。 而後對比每個ivar 和 B 對比,若是同樣那麼就能肯定啦,並且還能夠獲取實例B 的變量名
像這樣:
#import <objc/runtime.h> static id GetIvarName(id holdInstance,id ivarInstance){ if (!holdInstance||!ivarInstance) { return nil; } NSString *ivarName = nil; uint32_t ivarCount; Ivar *ivars = class_copyIvarList([holdInstance class], &ivarCount); if(ivars) { for(uint32_t i=0; i<ivarCount; i++) { Ivar ivar = ivars[i]; const char*ivarType = ivar_getTypeEncoding(ivar); NSString *ivarTypeStr = [NSString stringWithCString:ivarType encoding:NSUTF8StringEncoding]; //成員變量不是object 調用object_getIvar 會crash if (![ivarTypeStr hasPrefix:@"@"]) { continue; } id pointer = object_getIvar(holdInstance, ivar); if(pointer == ivarInstance) { ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; break; } } free(ivars); } return ivarName; }
上面例外狀況基本解決:
在hook 的 push 是 能夠肯定
if ([self.viewControllers count]>0) { UIViewController *lastVC = [self.viewControllers lastObject]; NSString *ivarName = GetIvarName(lastVC, viewController); viewController.isStrongRef = ivarName?YES:NO; if (viewController.isStrongRef) { DDLogWarn(@"****[%@]**被[%@]的成員變量[%@]強引用**,在%@ pop後將不會被釋放,若有必要,請忽略該條信息.",[viewController class],[lastVC class],ivarName,[viewController class]); } }
和 hook 的present中
NSString *ivarName = GetIvarName(self, viewControllerToPresent); viewControllerToPresent.isStrongRef = ivarName?YES:NO; if (viewControllerToPresent.isStrongRef) { DDLogWarn(@"****[%@]**被[%@]的成員變量[%@]強引用**,在%@ dismiss後將不會被釋放,若有必要,請忽略該條信息.",[viewControllerToPresent class],[self class],ivarName,[viewControllerToPresent class]); }
以上僅可用在debug 。。
不過遺憾的是,只能檢測nav uiviewController 相關的循環引用。應該能夠覆蓋大部分狀況啦。
代碼在這裏: git clone git@github.com:githhhh/ProbeRefCycles.git
好啦,小試牛刀後下篇 來試試 Instrument 這把利器 檢測循環引用....