Learn Objective-C, Objects (Part 2): Properties
Every object is made up of instance variables (iVars) and methods. Our Fraction
class, which we really began to build in the last post, contains two iVars, both NSIntegers
, called numerator
and denominator
. Fraction also has two methods, setNumerator:
and setDenominator:
. These methods will set a value to the corresponding instance variable. In most cases, you would also have a method, with the same name as the instance variable (yes, this is permitted, and in this case, encouraged), which would return the value of the instance variable. These setter and getter methods are referred to as mutator and accessor methods, respectively. These methods are a major concept of object-oriented programming—data encapsulation, the notion that objects hide their instance variables.
At this moment, our Fraction class defines and implements the setter methods (note that we simply have not needed the getter methods so far). However, this would be difficult to do when we start working with large classes, which have many instance variables. This is also not very scalable; the same code to retrieve values for a small block of data stored on an iPhone should probably not be the same code used to retrieve a long string of data from a powerful desktop Mac or even from the internet. Fortunately, there is a very convenient concept that Objective-C provides, and this construct should be used wherever possible.
Properties
Properties replace the accessor methods for an object. Quite simply, our Fraction class’s interface now looks like this:
#import <Foundation/Foundation.h>
@interface Fraction : NSObject {
NSInteger numerator;
NSInteger denominator;
}
@property NSInteger numerator;
@property NSInteger denominator;
- (void)display;
@end
The general form of the property statement is @property type name;
Note that the setter (and if we had them, the getter) methods are no longer needed. We will have the compiler synthesize these methods for us, but placing this line in our implementation, right after the @implementation
line:
#import "Fraction.h"
@implementation Fraction
@synthesize numerator, denominator
- (void)display {
NSString *numeratorString = [[NSString alloc] initWithFormat:@"%d", self.numerator];
NSString *denominatorString = [[NSString alloc] initWithFormat:@"%d", self.denominator];
NSLog(@"%@/%@", numeratorString, denominatorString);
[denominatorString release];
[numeratorString release];
}
@end
Of course, the identifiers after the @synthesize
have to match the names you declared as properties in the interface. However, the identifiers that you declared after @property
do not have the match the instance variables; instance variables will be created at compile time if this happens.
You do not have to change your method calls in your main()
routine; the methods still exist. However, there is an easier way to access these synthesized properties.
The Dot Operator
As previously mentioned, a getter method returns the value of an iVar; a setter sets a value to the iVar.
[myFraction setNumerator: 2] // Set numerator to 2 NSInteger
numeratorValue = [myFraction numerator]; // returns the value of the numerator, and set to the new variable called numeratorValue
The dot operator, introduced in Objective-C 2.0 (yes, there was a 1.0, but that was a long time ago), allows you to invoke the getter method by writing iVar.property
and the setter by iVar.property = value
Therefore, the above example can be re-written as
myFraction.numerator = 2; // Set numerator to 2
NSInteger numeratorValue = myFraction.numerator; // set value of numerator to numeratorValue
C programmers might recognize this syntax as being used to access members of a struct- because that is exactly what you are doing. Behind the scenes, an object’s instance variables are stored as a struct. And if you have no idea what this line means, don’t worry- structs are a basic C data type that we may cover in a later lesson.
Property Attributes
In a property declaration, you can specify any number of (non-contradictory) attributes:
@property (attribute1, attribute2, etc.) type name;
A list of possible attributes follows
Accessor Method Names
The default names for the getter and setter methods associated with a property are propertyName
and setPropertyName:
respectively- for example, given a property “foo”, the accessors would be foo
and setFoo:
. The following attributes allow you to specify custom names instead. They are both optional and may appear with any other attribute (except for readonly
in the case of setter=
).
-
getter=getterName
: Specifies the name of a different (custom) getter method for the property. The method must return a value matching the iVar’s type, and it takes no arguments. -
setter=setterName
: Specifies the name of a different (custom) setter method for the property. The method must return void, and it takes one argument of the same type as the iVar.If you specify that a property is readonly then also specify a setter withsetter=
, you will get a compiler warning.
Writability
These attributes specify whether or not a property has an associated set accessor. They are mutually exclusive.
-
readwrite
: This is the default; this indicates that the property will have read and write capabilities. When synthesized, both the getter and setter will be generated. -
readonly
: This indicates that the property can only be read; no data can be assigned to it (through the property). When synthesized, only a getter will be generated; when you try to assign the property a value using dot syntax, a compiler error will be generated.
Setter Semantics
These attributes specify the semantics of the setter. They are mutually exclusive.
-
assign
: This is the default; it tells the setter to directly assign the value. This is typically delineated with primitive data types (ints, floats, etc.) -
retain
: Specifies that a retain should be called on the object upon assignment; the previous value is released. This is only valid for objects. This has to do with memory management, a topic that will be fully explored in a later lesson. -
copy
: Specifies that the argument passed in should be copied, and the copy will be assigned. The previous value is released. This is only valid for objects. It is used in the case where the original property (perhaps stored on a shared server across the network) should not be modified by any other code, but when other code requests the value, for potential modification.
Atomicity
Atomicity means that the value is written to a temporary location in memory first, and then written to the intended location. This ensures that there is always a valid copy of the data somewhere. If an assignment was not atomic, and something happened during the writing period, the data could become fatally corrupt- the user would lose data. By writing data atomically, the data will either be the original, or the new value- never in a partially written state.
nonatomic
: Specifies that the object should not be assigned atomically. By default, accessors are atomic (this is more beneficial in a multi-threaded environment, where multiple threads may be reading/writing value to a piece of memory.
CS193P Material
Properties are a very powerful notion, and used throughout the language. If this explanation was not clear enough, please see Lecture 3 of CS193P. The relevant content begins around the 16 minute mark, and ends with a demo around the 45 minute mark. Of course, feel free to view the whole lecture.
The slides for the lecture are available on the CS193P website; for a direct link, click here.
The code for the Fraction class at this moment can be found here.
This post is part of the Learn Objective-C in 24 Days course.
Previous Lesson | Next Lesson |