Remember: instrument's leaks tool does not detect all memory leaks

Keywords: network Google


iu

Written in front

There are many articles on using instrument ed leaks tools to detect memory leaks on the Internet, but almost no one mentions one detail: not all memory leaks can detect.

I used to think that if I used the leaks tool to run back and forth several times, I would be successful without showing a memory leak until I packaged a module the other day:


Encapsulated UIScrollView placeholder.gif

Memory did not decrease after view removeFromSuperView was found. I keep showing and removing the placeholders, but the memory keeps increasing.


Memory only increases but not decreases

Regardless of whether memory leaks or not, there must be a problem that memory does not decrease as we expected after removeFromSuperView.

This is the code encapsulating the placeholder map, which is the category of UIScrollView:

Full demo is here

@implementation UIScrollView (PlaceholderView)

/**
 A placeholder map showing UIScrollView and its subclasses
 
 @param type Occupancy map type
 @param reloadBlock Reload the callback block
 */
- (void)showPlaceholderViewWithType:(CQPlaceholderViewType)type reloadBlock:(void (^)())reloadBlock {
    //Background view-//
    UIView *bgView = [[UIView alloc] initWithFrame:self.frame];
    [self.superview addSubview:bgView];
    bgView.backgroundColor = [UIColor whiteColor];
    
    //Icon-//
    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));
    }];
    
    //Description-//
    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);
    }];
    
    //---------------------------------------------------------------------------------------------------------------- reloading button-//
    UIButton *reloadButton = [[UIButton alloc] init];
    [bgView addSubview:reloadButton];
    [reloadButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [reloadButton setTitle:@"Reload" forState:UIControlStateNormal];
    reloadButton.layer.borderWidth = 1;
    reloadButton.layer.borderColor = [UIColor blackColor].CGColor;
    
    [[reloadButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        // Execute block callbacks
        if (reloadBlock) {
            reloadBlock();
        }
        // Remove from parent view
        [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));
    }];
    
    //Setting different UI s according to type-//
    switch (type) {
        case CQPlaceholderViewTypeNoNetwork: // The network is not good
        {
            NSString *path = [[NSBundle mainBundle] pathForResource:@"No net" ofType:@"png"];
            imageView.image = [UIImage imageWithContentsOfFile:path];
            descLabel.text = @"Network anomaly";
        }
            break;
            
        case CQPlaceholderViewTypeNoGoods: // No commodity
        {
            NSString *path = [[NSBundle mainBundle] pathForResource:@"No commodity" ofType:@"png"];
            imageView.image = [UIImage imageWithContentsOfFile:path];
            descLabel.text = @"Not a single item.";
        }
            break;
            
        case CQPlaceholderViewTypeNoComment: // No comment
        {
            NSString *path = [[NSBundle mainBundle] pathForResource:@"Sofa" ofType:@"png"];
            imageView.image = [UIImage imageWithContentsOfFile:path];
            descLabel.text = @"Grab the sofa!";
        }
            break;
            
        default:
            break;
    }
}

@end

The problem is in the code where the "reload" button clicks:

    [[reloadButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        // Execute block callbacks
        if (reloadBlock) {
            reloadBlock();
        }
        // Remove from parent view
        [bgView removeFromSuperview];
    }];

In response to this question, I went to ask, and got the answer is: In RAC subscribeNext will copy strongly refer to nextBlock, so it will strongly refer to reloadBlock and bgView. (If the statement is inappropriate, we hope to point it out directly.)

Now the problem is clear: Every time you display a placeholder, you create a new bgView, but the bgView removeFromSuperView is not released, so the memory only increases.

So how to solve this problem?

bgViewweak:

@weakify(bgView);
[[reloadButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    @strongify(bgView)
    // Execute block callbacks
    if (reloadBlock) {
        reloadBlock();
    }
    // Remove from parent view
    [bgView removeFromSuperview];
}];

Looking at the changes in memory, it's finally the same as expected:


Memory increases or decreases

Now accountability instrument'leaks

The released memory is not released. This is the memory leak. Why don't you remind me?

instrument ' leaks:


Well, since it doesn't answer seriously, I'll find out for myself:
I went to Google and found it. This article by Wechat Reading Team:

As you can see from Apple's developer documentation, the memory of an app falls into three categories:

  • 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 and Abandoned memory belong to the memory that should be released but not released. They are all memory leaks. Leaks tools are only responsible for detecting Leaked memory, regardless of Abandoned memory. Leaked memory is very common in the era of MRC, because it is easy to forget to call release, but in the era of ARC, the more common memory leak is Abandoned memory caused by circular reference. Leaks tool can not detect such memory leaks, and its application is limited.

The key point is this sentence:

A more common memory leak in the ARC era was Abandoned memory caused by circular references, which Leaks tools could not detect.

It seems that I have always misunderstood the leaks tool: other people simply do not have the ability to detect all memory leaks, but you have to force others to detect all memory leaks.

For Abandoned memory, it can be detected by Instrument's Alocations.

Search Allocations online, there should be a lot of use of tutorials, and students (including me) who only use leaks tools to check memory leaks can go and see.

summary

When using instrument's leaks tool to check memory leaks, we should also pay attention to the changing trend of memory.

Whether it is released depends on whether dealloc is executed, not only the dealloc of the view controller, but also the view in the view controller.

The problems we encounter in death will eventually be received and impressed.

demo with Memory Leakage in the Text

Posted by Bilbozilla on Wed, 22 May 2019 11:54:47 -0700