liuyaodong
liuyaodong

Reputation: 2567

iOS ARC - Why objects not be released immediately?

Maybe this is NOT a duplicate question, as I have searched and tried many solutions about how to release objects under ARC.
The code is simple:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self recreateView];

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
    [self.view addGestureRecognizer:tap];
}

- (void)tapped:(UITapGestureRecognizer *)g
{
    [self recreateView];
}

- (void)recreateView
{
    @autoreleasepool {
        for (UIView *v in self.view.subviews) {
            [v removeFromSuperview];
        }
        MyView *vv = [[MyView alloc] initWithFrame:self.view.bounds];
        [self.view addSubview:vv];
    }
    [self _performHeavyWork];
}

- (void)_performHeavyWork
{
    int j = 0;
    for (int i = 0 ; i < 100000000; ++i) {
        j += random() % 7;
        j = j % 18747;
    }

}
@end

ViewController simply add a tap gesture recognizer whose action is to remove the old subview before adding a new one. MyView is a subclass of UIView which simply log a message when dealloced.

@implementation MyView
- (void)dealloc
{
    NSLog(@"dealloc");
}
@end

The only magic is that the -_performHeavyWork is called every time a new view is created. When you keep on tapping on the screen quickly, the ViewController will be busy creating and discarding views. However, the odd thing is that all the discarded views are not dealloc immediately, but at the time you have stopped tapping for a while.
This is the profile of the process: Allocation Profile

As you can see, the memory keep growing if you keep on tapping and so many of MyView instances exist at the same time. And if you comment out [self _performHeavyWork];, everything will be back to normal. So my question is:

  1. Why do this happen?
  2. And how can I solve it?

Upvotes: 2

Views: 1232

Answers (3)

Scott Ahten
Scott Ahten

Reputation: 1171

First, iOS keeps a reference to a view when you add it to the visible window's view hierarchy. So, when you create a new instance your UIView subclass in the block, it remains in memory beyond the autorelease block. Second, the call to removeFromSuperview: does not actually result in the view being released by ARC until the main thread completes, which means there is still a reference to the view after the autorelease block ends. The work you're performing delays the main thread. This delays removing the final reference to the view.

Also, the autorelease block will not help in the case of removing the view because the view in question was not allocated in the same instance of the autorelease block. IOW, the view being created and added to the view hierarchy is not in the same scope when being removed later in the same block. So, there is no benefit to having the remove call in the autorelease block.

Upvotes: 0

Stephen Darlington
Stephen Darlington

Reputation: 52565

I think the main problem is that you're performing heavy work on the main thread. If you put the hard stuff on a different thread (or GCD) you'll probably see what you're expecting.

Here's some speculation on what's happening.

iOS responds to changes in the UI exclusively on the main thread. So if you're using the main thread for something else, the taps get queued for later processing.

You tap the screen, the main thread starts processing your heavy work.

You tap the screen some more. iOS can't deal with your request so it queues the event.

Eventually your heavy work completes and returns control to iOS.

iOS takes the queue of events and processes them all in a single run loop, which means the main loops auto release pool is never drained.

But what about the manual auto release pool? Well, all UI related stuff happens on the main loop and on the main thread, so the removeFrmSuperview: won't happen until control returns to the OS. Until that happens, the view hierarchy still holds a reference to your views, hence the memory growth.

Upvotes: 3

gnasher729
gnasher729

Reputation: 52538

You should never rely on dealloc being called when you think the last reference is gone. It is quite possible that references are still there where you don't expect them, but most importantly, dealloc can be called by ARC on a background thread.

Upvotes: 0

Related Questions