viewController被POP后不调用dealloc的问题

作者: 夜幕青雨 | 来源:发表于2016-05-26 20:07 被阅读9875次

    ARC下可以重写dealloc方法并在viewController被释放后自动调用,重写该方法时不能显式调用[super dealloc],因为系统会自动帮你调用父类的dealloc方法。

    控制器在被pop后移出栈后会被释放,但有些时候会发现控制器出栈的时候不会调用dealloc方法,归根结底,是因为当前控制器被某个对象强引用了,控制器的引用计数不为0,系统无法帮你释放这部分内存。原因大致有以下几点:

    控制器中NSTimer没有被销毁
    当viewController中存在NSTimer时,需要特别注意,当调用

    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:)  userInfo:nil repeats:YES]
    

    时,因为 target:self ,也就是引用了当前viewController,导致控制器的引用计数加1,如果没有将这个NSTimer 销毁,它将一直保留该viewController,无法释放,也就不会调用dealloc方法。所以,需要在viewWillDisappear之前需要把控制器用到的NSTimer销毁。

    [timer invalidate]; // 销毁timer
    timer = nil; // 置nil
    

    viewController中的代理不是weak属性
    例如@property (nonatomic, weak) id delegate;代理要使用弱引用,因为自定义控件是加载在视图控制器中的,视图控制器view对自定义控件是强引用,如果代理属性设置为strong,则意味着delegate对视图控制器也进行了强引用,会造成循环引用。导致控制器无法被释放,最终导致内存泄漏。

    viewController中block的循环引用
    在ARC下,block会把它里面的所有对象强引用,包括当前控制器self,因此有可能会出现循环引用的问题。比如viewController中有个block属性,在block中又强引用了self或者其他成员变量,那么这个viewController与自己的block属性就形成循环引用,导致viewController无法释放。

    // ARC enabled
    /************** MyObject Class **************/
    
    typedefvoid(^myBlock)(void);
    @interfaceMyObject:NSObject
    {
        myBlock blk;
    }
    @end
    
    @implementationMyObject
    - (id)init
    {
        self=[superinit];
        blk = ^{
                NSLog(@"self = %@",self);
        };
        returnself;
    }
    - (void)dealloc
    {
        NSLog(@"dealloc");
    }
    @end
    /************** main function **************/
    int main()
    {
        id myObject=[[MyObjectalloc] init];
        NSLog(@"%@",myObject);
        return 0;
    }
    

    由于self是__strong修饰,在 ARC 下,当编译器自动将代码中的 block 从栈拷贝到堆时,block 会强引用和持有self,而self恰好也强引用和持有了 block,就造成了传说中的循环引用。

    ddddd .png

    由于循环引用的存在,造成在main()函数结束时,内存仍然无法释放,即内存泄露。编译器也会给出警告信息

    warning: capturing 'self' strongly in this block is likely to lead to a retain cycle [-Warc-retain-cycles]
    blk = ^{NSLog(@"self = %@", self);};
    note: Block will be retained by an object strongly retained by the captured object
    blk = ^{NSLog(@"self = %@", self);};
    

    为了避免这种情况发生,可以在变量声明时用__weak修饰符修饰变量self,让 block 不强引用self,从而破除循环。iOS4 和 Snow Leopard 由于对 weak 的支持不够完全,可以用__unsafe_unretained代替。

    - (id)init
    {
        self = [super init];
        id __weak tmp = self;
        blk = ^{NSLog(@"self = %@", tmp);}; 
        return self;
    }
    
    Paste_Image.png

    再看一个例子:

    @interface MyObject : NSObject
    {
       myBlock  blk;
       id _obj; 
    }
    @end
    @implementation MyObject 
    
    - (id)init
    {
         self = [super init];
         blk = ^{ NSLog(@"_obj = %@", _obj); }; 
         return self;
    }
    ...
    ...
    @end
    

    上面的例子中,虽然没有直接使用 self,却也存在循环引用的问题。因为对于编译器来说,_obj就相当于self->_obj,所以上面的代码就会变成

     blk = ^{ NSLog(@"_obj = %@", self->_obj); };
    

    参考文章 [block没那么难(三):block和对象的内存管理]

    相关文章

      网友评论

      • 超_iOS:[avc push bvc]的话,avc是不是不应该走dealloc方法啊?
        夜幕青雨:avc应该是个容器,只要还有对象引用它,就不会被销毁。
      • 西叶lv:写了无限递归方法,也会造成界面无法销毁
        夜幕青雨:@郝嘉律 无限递归内存消耗,就直接crash了
      • 嗨哟:A push到B,内存增多,从B pop回来的时候有执行dealloc,但是内存并没有减少,这是为什么呢?
      • 解忧杂货店老板:建议写一写 view、number、delegate、cgfloat.....。等等类型,应该用什么属性来修饰。
        夜幕青雨:@解忧杂货店老板 这个应该有很多介绍了吧😂
      • Pandaxx:ViewController pop的时候没有释放,然后重新Push进去的时候释放了是什么原因
        1d5cb7cff98d:@1454123008 同问,这问题解决了吗?
        64fb2e84de0b:@Pandaxx 兄弟你解决了吗 我的和你一样
        d2726e8ce834:@Pandaxx 怀疑强引用持有导致,看看相关变量换成局部试下

      本文标题:viewController被POP后不调用dealloc的问题

      本文链接:https://www.haomeiwen.com/subject/dzsxdttx.html