zoukankan      html  css  js  c++  java
  • 检测项目中的循环引用引起的内存问题

    说到检测项目中的循环引用 可以有很多手段,其中牛叉的 instruments 当然是把利器。

    当然开发过程中往往会大意引起的 循环引用

    比如:忘写了 @weakify(self) && @strongify(self); 在大量使用RAC 和 block.....

    当然引起这类原因还有很多...

    如果分工明确的话可能会再项目结束后,专门测试这块...然而好像并不是每次迭代都会做这块的工作,除非被明确发现引起崩溃的情况。

    so  要是能把这个工作引入debug 期间,如果引起循环引用 可以抛出异常给开发人员,及时修复 

    这方面还是有些大牛写的工具的:如 FLEX 

    还有 HeapInspector-for-iOS  可以去看看

    但有个缺陷时都需要开发人员自己进行分析(),否则还是无法暴露这类问题。

    so  还是自己动手吧!

    切入点在 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 这把利器 检测循环引用....

  • 相关阅读:
    解决xcode5升级后,Undefined symbols for architecture arm64:问题
    第8章 Foundation Kit介绍
    app 之间发送文件 ios
    iphone怎么检测屏幕是否被点亮 (用UIApplication的Delegate)
    CRM下载对象一直处于Wait状态的原因
    错误消息Customer classification does not exist when downloading
    How to resolve error message Distribution channel is not allowed for sales
    ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX这些东东是什么鬼
    有了Debug权限就能干坏事?小心了,你的一举一动尽在系统监控中
    SAP GUI和Windows注册表
  • 原文地址:https://www.cnblogs.com/DamonTang/p/4923737.html
Copyright © 2011-2022 走看看