Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSArray or NSMutableArray possible memory leak using ARC [duplicate]

BKObject is a custom object and I want to put mutiple BKObject in to an array.

BKViewController:

#import <UIKit/UIKit.h>
#import "BKObject.h"

@interface BKViewController : UIViewController

@property (strong, nonatomic) NSArray *data;
@property (weak, nonatomic) BKObject *tmpObject;

@end

BKViewController.m:

#import "BKViewController.h"

@implementation BKViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSMutableArray *arr = [[NSMutableArray alloc] init];
    for(NSInteger i = 0; i < 100000; i++){
        [arr addObject:[[BKObject alloc] initWithName:@""]];
    }

    self.data = [NSArray arrayWithArray:arr];

    __weak BKObject *weakMutableObject = arr[0];
    [arr removeAllObjects];
    NSLog(@"%@", weakMutableObject); // print out the object, why?

    __weak BKObject *weakObject = self.data[0];
    self.data = nil;
    NSLog(@"%@", weakObject); // print out the object again, but why?


    self.tmpObject = [[BKObject alloc] initWithName:@""];
    NSLog(@"%@", self.tmpObject); // print null, very clear

}


@end

I'm curious about why the first 2 NSLog messages show an object instead of null(as in the last NSLog). I'm using the latest Xcode 5.0.1 with iOS 7 SDK.

like image 520
benck Avatar asked Mar 21 '23 21:03

benck


1 Answers

NSMutableArray *arr = [[NSMutableArray alloc] init];
for(NSInteger i = 0; i < 100000; i++){
    [arr addObject:[[BKObject alloc] initWithName:@""]];
}

OK, so at this point, we have a bunch of objects retained by an array.

self.data = [NSArray arrayWithArray:arr];

And now at this point, we have a bunch of objects retained by two different arrays.

__weak BKObject *weakMutableObject = arr[0];
[arr removeAllObjects];
NSLog(@"%@", weakMutableObject); // print out the object, why?

Because the object pointed to by arr[0] is also retained by self.data.

__weak BKObject *weakObject = self.data[0];
self.data = nil;
NSLog(@"%@", weakObject); // print out the object again, but why?

This one is a bit interesting. The "problem" is that arrayWithArray: is adding an extra retain/autorelease, which it's free to do since they're balanced. You can demonstrate that pretty simply by draining the autorelease pool at different points.

This shows a live object:

  __weak NSObject *weakObject;
  self.data = [NSArray arrayWithArray:arr]; // Note outside nested autorelease pool
  @autoreleasepool {
    ...    
    weakObject= self.data[0];
    self.data = nil;
  }
  NSLog(@"%@", weakObject); // print out the object

This shows nil:

  __weak NSObject *weakObject;
  @autoreleasepool {
    self.data = [NSArray arrayWithArray:arr]; // Note inside nested autorelease pool
    ...   
    weakObject= self.data[0];
    self.data = nil;
  }
  NSLog(@"%@", weakObject); // print nil

The lesson here is that you should not assume that an object will deallocate at any given point within an autorelease block. That is not a promise ARC gives. It only promises a minimum amount of time that the object will be valid. Other parts of the system are free to attach balanced retain/autorelease pairs as much as they like, which will delay deallocation until the pool drains.

like image 137
Rob Napier Avatar answered Apr 19 '23 23:04

Rob Napier