美文网首页iOS开发精进iOS开发实用技术iOS-架构优化
记住:instrument的leaks工具并不能检测出所有的内存

记住:instrument的leaks工具并不能检测出所有的内存

作者: Lol刀妹 | 来源:发表于2017-09-23 10:53 被阅读421次
    iu

    写在前面

    网上关于用instrument的leaks工具检查内存泄漏的文章很多,但是几乎没人提到一个细节:不是所有的内存泄漏都是leaks工具能够检测出来的

    我之前也一直以为,只要用leaks工具来回跑几次,没有显示内存泄漏就大功告成了,直到前几天我封装一个模块的时候:

    封装的UIScrollView占位图.gif

    发现view removeFromSuperView后内存并没有降低。我不断的展示又移除占位图,但是内存都是只增不减:

    内存只增不减

    这里先不管是不是内存泄漏,removeFromSuperView后内存没有按照我们所期望的降低,肯定就是有问题的。

    这是封装占位图的代码,是UIScrollView的category:

    完整demo在这里

    @implementation UIScrollView (PlaceholderView)
    
    /**
     展示UIScrollView及其子类的占位图
     
     @param type 占位图类型
     @param reloadBlock 重新加载回调的block
     */
    - (void)showPlaceholderViewWithType:(CQPlaceholderViewType)type reloadBlock:(void (^)())reloadBlock {
        //------- 背景view -------//
        UIView *bgView = [[UIView alloc] initWithFrame:self.frame];
        [self.superview addSubview:bgView];
        bgView.backgroundColor = [UIColor whiteColor];
        
        //------- 图标 -------//
        UIImageView *imageView = [[UIImageView alloc] init];
        [bgView addSubview:imageView];
        [imageView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.mas_equalTo(imageView.superview);
            make.centerY.mas_equalTo(imageView.superview).mas_offset(-80);
            make.size.mas_equalTo(CGSizeMake(70, 70));
        }];
        
        //------- 描述 -------//
        UILabel *descLabel = [[UILabel alloc] init];
        [bgView addSubview:descLabel];
        [descLabel mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.mas_equalTo(descLabel.superview);
            make.top.mas_equalTo(imageView.mas_bottom).mas_offset(20);
            make.height.mas_equalTo(15);
        }];
        
        //------- 重新加载button -------//
        UIButton *reloadButton = [[UIButton alloc] init];
        [bgView addSubview:reloadButton];
        [reloadButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [reloadButton setTitle:@"重新加载" forState:UIControlStateNormal];
        reloadButton.layer.borderWidth = 1;
        reloadButton.layer.borderColor = [UIColor blackColor].CGColor;
        
        [[reloadButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
            // 执行block回调
            if (reloadBlock) {
                reloadBlock();
            }
            // 从父视图移除
            [bgView removeFromSuperview];
        }];
        [reloadButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.mas_equalTo(reloadButton.superview);
            make.top.mas_equalTo(descLabel.mas_bottom).mas_offset(20);
            make.size.mas_equalTo(CGSizeMake(120, 30));
        }];
        
        //------- 根据type设置不同UI -------//
        switch (type) {
            case CQPlaceholderViewTypeNoNetwork: // 网络不好
            {
                NSString *path = [[NSBundle mainBundle] pathForResource:@"无网" ofType:@"png"];
                imageView.image = [UIImage imageWithContentsOfFile:path];
                descLabel.text = @"网络异常";
            }
                break;
                
            case CQPlaceholderViewTypeNoGoods: // 没商品
            {
                NSString *path = [[NSBundle mainBundle] pathForResource:@"无商品" ofType:@"png"];
                imageView.image = [UIImage imageWithContentsOfFile:path];
                descLabel.text = @"一个商品都没有";
            }
                break;
                
            case CQPlaceholderViewTypeNoComment: // 没评论
            {
                NSString *path = [[NSBundle mainBundle] pathForResource:@"沙发" ofType:@"png"];
                imageView.image = [UIImage imageWithContentsOfFile:path];
                descLabel.text = @"抢沙发!";
            }
                break;
                
            default:
                break;
        }
    }
    
    @end
    

    问题就出在“重新加载”按钮点击的那段代码里:

        [[reloadButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
            // 执行block回调
            if (reloadBlock) {
                reloadBlock();
            }
            // 从父视图移除
            [bgView removeFromSuperview];
        }];
    

    针对这个问题,我去问了一下,得到的答案是:RAC中subscribeNext会copy强引用nextBlock, 所以会对reloadBlock和bgView强引用(如果说法欠妥希望直接指出)。

    现在问题就很明确了:每次展示占位图都新建了一个bgView,但是这个bgView removeFromSuperView后却没有释放,因此导致内存只增不减。

    那么如何解决这个问题?

    bgViewweak即可:

    @weakify(bgView);
    [[reloadButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        @strongify(bgView)
        // 执行block回调
        if (reloadBlock) {
            reloadBlock();
        }
        // 从父视图移除
        [bgView removeFromSuperview];
    }];
    

    再看内存变化情况,终于和期望的一样了:

    内存有增有减

    现在问责instrument ' leaks

    该释放的内存没有释放,这就是内存泄漏,为什么你不提醒我?

    instrument ' leaks:

    好吧,既然它不正经回答那我就自己找答案:
    我Google了一下,找到微信读书团队的这篇文章:

    从苹果的开发者文档里可以看到,一个 app 的内存分三类:

    • Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).
    • Abandoned memory: Memory still referenced by your application that has no useful purpose.
    • Cached memory: Memory still referenced by your application that might be used again for better performance.

    其中 Leaked memory 和 Abandoned memory 都属于应该释放而没释放的内存,都是内存泄露,而 Leaks 工具只负责检测 Leaked memory,而不管 Abandoned memory。在 MRC 时代 Leaked memory 很常见,因为很容易忘了调用 release,但在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限。

    重点就是这句:

    在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露。

    看来是我一直误会leaks工具了:别人根本就不具备检测所有内存泄漏的能力,你却要强行用别人来检测所有内存泄漏。

    对于 Abandoned memory,可以用 Instrument 的 Allocations 检测出来。

    网上搜一下Allocations,应该有很多使用教程,还在只用leaks工具检查内存泄漏的同学(包括我)可以去看看。

    总结

    用instrument的leaks工具检查内存泄漏的时候还应该注意内存的变化趋势。

    是否真的释放还得看是否执行dealloc,不单单是视图控制器的dealloc,还有视图控制器里的view。

    死磕遇到的问题,终会有收货,而且印象深刻。

    文中内存泄漏的demo

    相关文章

      网友评论

      • cdfc7672b68f:开一个allocation和leaks 同时检测,不然真的一遍过就完蛋了
        Lol刀妹:学习了👍
      • youlianchun:是的,leaks 并不是很准确的,还是要自己代码上怎么写多下点功夫,不能太依赖
        Lol刀妹:@youlianchun 是的,写好代码才是王道

      本文标题:记住:instrument的leaks工具并不能检测出所有的内存

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