From: http://useyourloaf.com/blog/2011/2/8/understanding-your-objective-c-self.html
A frequent question I see from people new to iOS programming is why access to an objects instance variables (ivars) sometimes goes via the self object (but not always). For example consider a simple table view controller that contains an NSDate object defined as follows:
@interface RootViewController : UITableViewController {
NSDate *timestamp;
}
@property (nonatomic, retain) NSDate *timestamp;
The view controller typically then contains code similar to the following:
@implementation RootViewController
@synthesize timestamp;
- (void)viewDidLoad
{
[super viewDidLoad];
self.timestamp = [NSDate date];
}
- (void)viewWillAppear:(BOOL)animated
{
self.timestamp = [NSDate date];
[self.tableView reloadData];
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.timestamp = nil;
}
- (void)dealloc
{
[timestamp release];
[super dealloc];
}
So why does this code use self.timestamp everywhere except the dealloc method? To understand what is going on you need to understand something about Objective 2.0 properties, accessors and the dot syntax. That is quite a big topic in itself so I will to summarise the key points.
Objective-C Properties
The property declaration for the timestamp object is as follows:
@property (nonatomic, retain) NSDate *timestamp;
The important point to notice is that we have specified retain to indicate that when we set the ivar we should retain the object we are assigning (and of course release any old object we were previously retaining). The implementation of RootViewController includes an @synthesize statement so the compiler will automatically generate getter and setter methods for us that will respect this requirement. Typical getter and setter methods would look like this:
- (NSDate *)timestamp
{
return timestamp;
}
- (void)setTimestamp:(NSDate *)newValue
{
if (timestamp != newValue)
{
[timestamp release];
timestamp = [newValue retain];
}
}
The getter is not interesting since it just returns the value of the ivar. The setter method is more interesting since it needs to practise safe memory management. This means whenever we assign a new object we first release the old object and then ensure we retain the new object so that it does not get deallocated before we are finished with it.
To use the setter to store a new NSDate object in our view controller we could write something like this:
[self setTimestamp:myNewDate];
This is sending the message setTimestamp to the self object where the self object refers to the RootViewController object. This ends up calling our setter method to safely assign and retain the new object. However there is an alternative to this setter method syntax.
Objective-C Dot Syntax
The dot syntax was introduced with Objective-C 2.0 and generates a lot of debate. A number of experienced and long time Cocoa programmers recommend avoiding it completely. Others such as Chris Hanson have a different view about when to use properties and dot notation. Whichever side of the argument you fall I guess the main thing is to be consistent.
Anyway the main thing to understand about the dot syntax is that the following two statements are doing the same thing:
self.timestamp = [NSDate date];
[self setTimestamp:[NSDate date]];
The dot is just a shortcut for the more traditional Objective-C method call. Any time you see a dot you can replace it with the equivalent square bracket method call syntax. It is important to understand however that this is not the same as writing the following:
timestamp = [NSDate date];
Without the self object and the dot we are no longer sending an object a message but directly accessing the ivar named timestamp. Since this bypasses the setter method we will overwrite the timestamp ivar without first releasing the old NSDate object. We will also not retain the new object that we are assigning. Both of these situations are bad!
Putting it all together
If we walk through each of the view controller methods we saw at the start of this post it should now be clear why each access to timestamp is written the way it is:
Firstly the viewDidLoad and viewWillAppear methods both allocate a new date object and need to store a reference to this new object in the ivar of the view controller. Since we may already have an existing object allocated this first needs to be released before assigning the new value. In other words we need the setter method which we can invoke using the timestamp property:
self.timestamp = [NSDate date];
If we were to omit the self object in the first of those two statements (timestamp = [NSDate date];) we would be accessing the ivar directly which bypasses the setter and ends up leaking memory.
The viewDidUnload method is somewhat different in that it uses a shortcut to both release the NSDate object and then zero the ivar. If you refer back to the setter method you will see that it takes care of the release for us. This is of course true as long as we use the property, if we access the ivar directly we would need to also release the object as follows:
[timestamp release];
timestamp = nil;
which is somewhat longer than simply writing this:
self.timestamp = nil;
Finally the dealloc method which simply releases the NSDate object needs to access the ivar directly which is what [timestamp release] does.
Separating ivars from properties
There is a further option that I think is also worth mentioning at this point. By default when you synthesize the getter and setter accessor methods it is assumed that the property and ivar have the same name. This can make it confusing at first glance as to when you are using the getter/setter methods and when you are accessing the ivar directly. The alternative is name the ivar differently from the property. A common approach is to use an underscore to prefix the ivars names:
@interface RootViewController : UITableViewController {
NSDate *_timestamp;
}
@property (nonatomic, retain) NSDate *timestamp;
To connect the property (whose name has not changed) the sythensize statement gets an extra option:
@implementation RootViewController
@synthesize timestamp = _timestamp;
If instead of having the compiler automatically synthesize the getter and setter we were to write them ourselves they would now look something like this:
- (NSDate *)timestamp
{
return _timestamp;
}
- (void)setTimestamp:(NSDate *)newValue
{
if (_timestamp != newValue)
{
[_timestamp release];
_timestamp = [newValue retain];
}
}
The getter and setter both directly access the ivar which is now much clearer from the use of the _timestamp name. The rest of the code does not change very much since we do mostly want to always use the getter/setter methods with the exception of the dealloc method which needs to access the ivar directly:
- (void)dealloc
{
[_timestamp release];
[super dealloc];
}
The main advantage to this approach is if you forget and accidentally write something like this:
timestamp = [NSDate date];
This will generate a compiler error telling you that timestamp is undeclared. This is clearly better than directly assigning to the ivar and leaking memory. What I like about this approach is that it provides some additional compile time checking without requiring much in the way of additional code other than a slightly longer synthesize statement for each property.
No comments:
Post a Comment