檢測項目中的循環引用引發的內存問題

說到檢測項目中的循環引用 能夠有不少手段,其中牛叉的 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 這把利器 檢測循環引用....

相關文章
相關標籤/搜索