views:

674

answers:

4

This morning I ran into a crash in an iPhone app I am working on and while I fixed the bug, I'm curious about syntactic reason it was a problem.

Here is my code reduced to simple elements. I am populating items in a TableView using an NSArray for the items. The NSArray is a property:

@interface FooViewController : UITableViewController {
    NSArray *stuff;
}

@property (nonatomic, retain) NSArray *stuff;

And in my implementation file:

@synthesize stuff;

- (void)viewDidLoad {     
    NSArray *arr = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];
    self.stuff = arr;

    [arr release];
}

Now, when I first wrote the method, I accidentally left off the "self." and that caused the bomb. Although while testing, it worked at first blush. I'd tried:

stuff = arr;
NSLog(@"%d", [stuff count]);

But using stuff in other methods bombed. Now that I've fixed the problem, I can use [stuff count] in other places.

So why can I use stuff in some places but in others I must use self.stuff?

+2  A: 

stuff = ... directly references the backing field of the property. It doesn't increase the retain count. As a result, releasing the object somewhere else might take its retain count down to zero and have it deallocated while you're still holding a reference to it. Also, it may cause a memory leak for the previous value of the property.
The reason it looks like working sometimes is that the object is probably not deallocated yet, by someone else.

On the other hand, self.stuff = ... will send a message to the property's set accessor which will take care of retain count.

Mehrdad Afshari
+3  A: 

When you use (self) and dot syntax, given the way you defined the property (nonatomic, retain), the NSArray (stuff) is retained.

When you don't, you are still making the assignment, but you aren't retaining the array aside from the implicit retain via alloc+init - and you release it right away.

You can get around assigning via "self.stuff = arr" by doing:

stuff = [arr retain];

But since you defined a property, you obviously WANT to be using dot syntax and having the retain called for you.

Toby Joe Boudreaux
+2  A: 

This would have also worked properly:

- (void)viewDidLoad {     
    stuff = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];
}

Because the array was retained by alloc. But it's usually better to stick to the dot notation if you have a property and use the autorelease array creation methods, where you get retains "for free" from the property:

- (void)viewDidLoad {     
    NSArray *arr = [NSArray arrayWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];
    self.stuff = arr;

}

You probably just left it out to keep things simple, but you also need to free this array in dealloc:

- (void)dealloc {     
  [stuff release]; stuff = nil;
}
Kendall Helmstetter Gelner
If you are going to use properties, then you should use them everywhere, ie the dealloc contents should simply read: self.stuff = nil;A common way to help avoid making mistakes is to declare the instance variable as: NSArray *_stuff;The property declaration remains the same, but the synthesize statement should now look like this: @synthesize stuff = _stuff;Now every time you try to use stuff without the self the compiler will flag an error.
David Jacobson
Actually Apple recommends in dealloc to directly release class variables, not to use the properties - this is because if you use the properties you may be triggering KVC notifications, or possibly launching other side effects from overriding the synthesized get/set methods.
Kendall Helmstetter Gelner
@Kendall: Thanks for the tip. I wonder how relevant this is becoming, especially since might one not *want* the KVO triggering? Not to mention that in the modern runtime we will *have* to use the accessors with synthesized instance variables. I imagine Apple are making sure that their code protects against things like that. So far I haven't run into any problems with my code, but I'll be on the lookout now I've been warned :-)
David Jacobson
Not wanting the KVO triggering is a valid concern, Apple says to directly set values in init and directly release them in dealloc for just that reason. Since this was in viewDidLoad, I thought it might be more desirable... though I could see wanting to treat it like init. I'll have to think more myself if the approach should be changed with the modern runtime...
Kendall Helmstetter Gelner
+1  A: 

The difference between doing:

stuff=arr;

and

self.stuff=arr;

is that in the second case, you're actually calling the automatically-synthesized setStuff: accessor method, which retains the array. In the code you've posted, the array is being created with alloc/initWithObjects, so it already has a retain count of 1.

You just need to change remove the call to [arr release] in your viewDidLoad: method, and all will be well:

- (void)viewDidLoad {     
    NSArray *arr = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];
    stuff = arr;
}

As you noticed, you can also "fix" this by using self.stuff. I'd recommend against doing that, since it obscures the meaning of the code, and adds additional work that's not needed in most cases. In general, I recommend not using the "self." syntax in your instance methods.

Mark Bessey