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?
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.
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;
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With