Effective Objective-C 2.0:Item 7: Access Instance Variables

时间:2022-09-07 20:04:25

initializer: set 时候直接访问ivar;get时候看具体;

一般情况下,set用accessors; get用ivar快 

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.

Effective Objective-C 2.0:Item 7: Access Instance Variables 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.

Effective Objective-C 2.0:Item 7: Access Instance Variables 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.

Effective Objective-C 2.0:Item 7: Access Instance Variables 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.

Effective Objective-C 2.0:Item 7: Access Instance Variables 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

Effective Objective-C 2.0:Item 7: Access Instance Variables Prefer to read data directly through instance variables internally and to write data through properties internally.

Effective Objective-C 2.0:Item 7: Access Instance Variables Within initializers and dealloc, always read and write data directly through instance variables.

Effective Objective-C 2.0:Item 7: Access Instance Variables Sometimes, you will need to read data through properties when that data is being lazily initialized.