4

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.

benck
  • 2,034
  • 1
  • 22
  • 31

5 Answers5

5
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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Are you saying that those two objects(pointed by weakObject and weakMutableObject) will be dealloc after this function ends? – benck Oct 26 '13 at 18:18
  • Both `weakObject` and `weakMutableObject` point to the same object. That object will (given the current code) deallocate when the current autorelease pool drains. Since this is `viewDidLoad`, you should expect that to happen sometime shortly after the end of this method. It'll be before the next event loop begins in any case. – Rob Napier Oct 27 '13 at 19:54
2

With this line:

self.data = [NSArray arrayWithArray:arr];

You end up with two arrays and two strong references to your object. You then remove the objects from the first array, but not from the second. Thus the objects still have one strong reference and are still alive.

Remember, __weak is zeroed out when all strong references to the object have been removed. With the second array, you still have a strong reference for the first NSLog.

With the second NSLog, there is probably an autorelease involved with accessing the property that prevents the array from being released immediately. Edit: See Rob Napier's answer for details.

With the third log, you are setting:

self.tmpObject = [[BKObject alloc] initWithName:@""];

Where self.tmpObject is a weak reference. As you only ever have a weak reference to this object, the property is immediately zeroed out.

BergQuester
  • 6,167
  • 27
  • 39
  • I think it has nothing to do with self.data=[NSArray arraywitharray:arr]. Changing (weakMutableObject = arr[0]; and weakObject = self.data[0];) to (self.tmpObject = arr[0] and self.tmpobject = self.data[0]) print null for both cases. – benck Oct 26 '13 at 18:15
0

It's probably because you're still inside the same auto release pool. Which is scoped to your function. Try setting the weak reference outside the scope of the function (eg as a property) and calling your function to create and release inside another function and you should then see the objects release.

If you want to create and release a lot of objects inside a loop such as in your example consider doing so inside a custom release pool.

Have a look at: https://developer.apple.com/library/mac/documentation/cocoa/conceptual/memorymgmt/articles/mmAutoreleasePools.html

Bladebunny
  • 1,251
  • 9
  • 12
  • As you said, if the weak variable is outside the function, it's null. Thanks for the auto release pool information. – benck Oct 26 '13 at 18:11
0

This is the way objects works. You have created an object which was allocated a memory position and then it was put into a local NSArray which then keeps track of it and then it was put into one more local array before you finally put into instance variable (self.data). So at this time your object is technically having 3 as retain count so in your code you have released it twice which is the reason of it being printed in both your NSLog statement.

Try with below code:

NSString *a = @"1";

NSMutableArray *arr = [[NSMutableArray alloc] init];
for (int i = 0; i < 10000; i++) {
    [arr addObject:a]; // Retain count 1
}

self.myArr = arr; // Retain count 2
NSString *test = arr[0];
[arr removeAllObjects];
NSLog(@"%@", test); // Prints ... Good! Retain count is 1

NSString *test1 = self.myArr[0];
self.myArr = nil;
NSLog(@"%@", test1); // Crash as object gone
Abhinav
  • 37,684
  • 43
  • 191
  • 309
0

The problem is you are assigning your array value to some variable and then you are removing your array but in your nslog you are printing the variable in which you had assigned your array. So defintely it will not print null it will print object

  self.data = [NSArray arrayWithArray:arr];

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

the return value of a convenience constructor like this must be an autoreleased object*. That means the current autoreleasepool has retained the object and will not release it until the pool is drained. You are therefore all but guaranteed that this object will exist for at least the duration of your method - although you probably shouldn't rely on this behaviour.

Hussain Shabbir
  • 14,801
  • 5
  • 40
  • 56