检测项目中的循环引用引发的内存问题

说到检测项目中的循环引用 能够有不少手段,其中牛叉的 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 这把利器 检测循环引用....

相关文章
相关标签/搜索