Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding Instruments memory allocation log on iOS

I have built an iOS app that is almost done, however, I have recently experienced that it crashes after while due to "Memory pressure". So I started profiling the memory allocations in Instruments and sure, the app does use quite a lot of memory and it only seems to increase during usage.

However, relatively new to Instruments memory allocation I am not quite able to decipher where 52 % of the allocations are made, as seen in the screenshot below:

enter image description here

It has obviously got something to do with Core Animation, but what exactly is hard for me to determine, so I thought that some clever minds out there might know the answer to that.

Breadcrumb:

My app uses custom segues, when moving between view controllers, where a lot of animation is taking place. Here is an example:

@interface AreaToKeyFiguresSegue : UIStoryboardSegue

@end

...

@implementation AreaToKeyFiguresSegue

- (void)perform
{
    [self sourceControllerOut];
}

- (void)sourceControllerOut
{
    AreaChooserViewController *sourceViewController = (AreaChooserViewController *) [self sourceViewController];
    KeyFigureViewController *destinationController = (KeyFigureViewController *) [self destinationViewController];

    double ratio = 22.0/sourceViewController.titleLabel.font.pointSize;

    sourceViewController.titleLabel.adjustsFontSizeToFitWidth = YES;

    [UIView animateWithDuration:TRANSITION_DURATION delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{

        // Animate areaChooser
        sourceViewController.areaChooserScrollView.alpha = 0;
        sourceViewController.areaScrollViewVerticalSpaceConstraint.constant = -300;

        sourceViewController.backButtonVerticalConstraint.constant = 20;
        sourceViewController.backButton.transform = CGAffineTransformScale(sourceViewController.backButton.transform, ratio, ratio);
        sourceViewController.backButton.titleLabel.textColor = [UIColor redKombitColor];

        sourceViewController.backArrowPlaceholderVerticalConstraint.constant = 14;
        sourceViewController.backArrowPlaceholder.alpha = 1;

        sourceViewController.areaLabelVerticalConstraint.constant = 50;
        sourceViewController.areaLabel.alpha = 1;

        [sourceViewController.view layoutIfNeeded];

    } completion:^(BOOL finished) {
        [destinationController view]; // Make sure destionation view is initialized before animating it
        [sourceViewController.navigationController pushViewController:destinationController animated:NO]; // Push new viewController without animating it

        [self destinationControllerIn]; // Now animate destination controller
    }];
}

- (void)destinationControllerIn
{
    AreaChooserViewController *sourceViewController = (AreaChooserViewController *) [self sourceViewController];
    KeyFigureViewController *destinationController = (KeyFigureViewController *) [self destinationViewController];

    destinationController.keyFigureTableViewVerticalConstraint.constant = 600;
    destinationController.keyFigureTableView.alpha = 0.0;
    destinationController.allFavoritesSegmentedControl.alpha = 0.0;
    [destinationController.view layoutIfNeeded];
    [sourceViewController.segueProgress setHidden:YES];
} 

@end

And whenever a view controller is to be popped, I simply do the reverse thing:

- (IBAction)goBack:(id)sender
{
    [UIView animateWithDuration:TRANSITION_DURATION delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{

        [self.keyFigureTableView setAlpha:0];
        self.keyFigureTableViewVerticalConstraint.constant = 700;
        [self.allFavoritesSegmentedControl setAlpha:0];
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {
        [self.navigationController popViewControllerAnimated:NO]; // Pop viewController without animating it
    }];
}

Edit:

Most of the memory allocation takes place when pushing a view controller, even if it has already been displayed before. I.e. going from

A -> B -> C

B <- C

B -> C

where "->" = push and "<-" = pop, each "->" allocates more memory and "<-" never releases any.

Further details

I have no zombies and no leaks according to Instruments. Static analysis also gives nothing. My app just keeps allocating memory until it finally crashes.

Around 70 % of my memory allocation happens in the following call stack, which has nothing to do with my code (inverted call tree):

enter image description here

like image 201
Zappel Avatar asked Dec 09 '13 13:12

Zappel


People also ask

How detect memory leak iOS app?

To detect memory leaks you should run the app and navigate through all possible flows and open several times the same view controllers, then enter memory graph debugger and look at the memory heap. Look for objects that shouldn't be in memory, for example: A view controller that is no longer present in the app.

How does memory management work in iOS?

Memory management in iOS was initially non-ARC (Automatic Reference Counting), where we have to retain and release the objects. Now, it supports ARC and we don't have to retain and release the objects. Xcode takes care of the job automatically in compile time.

How do I check memory usage on my Iphone?

Use your device to check its storageGo to Settings > General > [Device] Storage. You might see a list of recommendations for optimizing your device's storage, followed by a list of installed apps and the amount of storage each one uses. Tap an app's name for more information about its storage.


1 Answers

Here's how I debug these.

  1. In Instruments, use the Allocations instrument and turn on "Record reference counts"

enter image description here

  1. Run your app to a "steady state", including performing the operation you think is leaking a few times.

  2. In Instruments, set your baseline memory level using the in/out markers.

  3. Perform the operation you think is leaking a few times. (say 7)

  4. In Instruments, switch to the view that shows all allocations and look for objects that have been allocated yet not deallocated the same number of times as the operation you just performed (again maybe 7 times). You'll first want to try to find an object specific to your program... So prefer MyNetworkOperation instances instead of generic foundation classes like NSData. click arrow next to class you are interested inclick arrow next to an allocated object

  5. Select one of the objects that hasn't been deallocated and look at it's allocation history. You'll be able to see the call stack for every alloc/retain/release/autorelease for the object in question.. Probably one of the calls will look suspicious to you. history of retain/release for selected object

I suppose these steps apply more in a non-ARC environment. Under ARC you might be looking for something that's a retain cycle.

In general you can avoid retain cycles by making sure your strong references only go in one direction... For example, a view has strong references to it's subviews and each subview must only ever use weak references to refer to any parent view. Or perhaps your view controller has a strong reference to your view. Your view must only have a weak reference to its view controller. Another way to say this is to decide in each relationship which object "owns" the other.

like image 158
nielsbot Avatar answered Sep 17 '22 15:09

nielsbot