initializer: set 时候直接访问ivar;get时候看具体;
Item 7: Access Instance Variables Primarily Directly When Accessing Them Internally
Properties should always be used to access instance variables of an object externally, but how you access instance variables internally is a hotly debated topic within the Objective-C community. Some suggest always using a property to access instance variables, some suggest always accessing the instance variable directly, and some suggest a mixture of the two. I strongly encourage you to read instance variables using direct access but to set them using the property, with a few caveats.
Consider the following class:
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
// Convenience for firstName + " " + lastName:
- (NSString*)fullName;
- (void)setFullName:(NSString*)fullName;
@end
The convenience methods fullName
and setFullName:
might be implemented like this:
- (NSString*)fullName {
return [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
}
/** The following assumes all full names have exactly 2
* parts. The method could be rewritten to support more
* exotic names.
*/
- (void)setFullName:(NSString*)fullName {
NSArray *components =
[fullName componentsSeparatedByString:@" "];
self.firstName = [components objectAtIndex:0];
self.lastName = [components objectAtIndex:1];
}
In both the getter and the setter, we access the instance variables via the accessor methods, using the property dot syntax. Now suppose that you rewrote both methods to access the instance variables directly:
- (NSString*)fullName {
return [NSString stringWithFormat:@"%@ %@",
_firstName, _lastName];
}
- (void)setFullName:(NSString*)fullName {
NSArray *components =
[fullName componentsSeparatedByString:@" "];
_firstName = [components objectAtIndex:0];
_lastName = [components objectAtIndex:1];
}
The two styles have a few differences.
Direct access to the instance variables will undoubtedly be faster, as it does not have to go through Objective-C method dispatch (see Item 11). The compiler will emit code that directly accesses the memory where the object’s instance variables are stored.
Direct access bypasses the property’s memory-management semantics defined by the setter. For example, if your property is declared as copy
, directly setting the instance variable will not cause a copy to be made. The new value will be retained and the old value released.
Key-Value Observing (KVO) notifications would not be fired when accessing the instance variables directly. This may or may not be a problem, depending on how you want your objects to behave.
Accessing through properties can make it easier to debug issues surrounding a property, since you can add a breakpoint to the getter and/or setter to determine who is accessing the properties and when.
A good compromise is to write instance variables using the setter and to read using direct access. Doing so has the benefit of fast reading and not losing the control of writing via properties. The most important reason for writing via the setter is that you will ensure that the memory-management semantics are upheld. There are, however, a few caveats to that approach.
The first caveat is when values are set within an initializer method. Here, you should always use direct instance variable access, because subclasses could override the setter. Consider that EOCPerson
has a subclass EOCSmithPerson
that is designed to be used only for people whose last name is “Smith.” This subclass might override the setter for lastName
like so:
- (void)setLastName:(NSString*)lastName {
if (![lastName isEqualToString:@"Smith"]) {
[NSException raise:NSInvalidArgumentException
format:@"Last name must be Smith"];
}
self.lastName = lastname;
}
The base class EOCPerson
might set the last name to the empty string in its default initializer. If it did this through the setter, the subclass’s setter would be called and throw an exception. However, there are some cases in which you must use the setterin an initializer. This is when the instance variable is declared within a superclass; you cannot access the instance variable directly anyway, so you must use the setter.
Another caveat is when the property uses lazy initialization. In this case, you have to go via the getter; if you don’t, the instance variable will never get a chance to be initialized. For example, the EOCPerson
class might have a property to give access to a complex object representing each person’s brain. If this property is infrequently accessed and expensive to set up, you might initialize it lazily in the getter, like this:
- (EOCBrain*)brain {
if (!_brain) {
_brain = [Brain new];
}
return _brain;
}
If you were to access the instance variable directly and the getter had not been called yet, brain
would not have been set up, and you would need to call the accessor for all accesses to the brain
property.
Things to Remember
Prefer to read data directly through instance variables internally and to write data through properties internally.
Within initializers and dealloc
, always read and write data directly through instance variables.
Sometimes, you will need to read data through properties when that data is being lazily initialized.