In the last post, I showed off the description, isEqual, and hash methods defined by the NSObject class. In this post, I’m going to look at another aspect of Objective-C known as properties.
If you’re coming from the .NET world, then I am guessing that you are familiar with properties. For example, in C#, I could define the Person class like this:
public class Person
{
// instance fields
private string firstName;
private string middleName;
private string lastName;
// properties
public string FirstName
{
get { return this.firstName; }
set { this.firstName = value; }
}
public string MiddleName
{
get { return this.middleName; }
set { this.middleName = value; }
}
public string LastName
{
get { return this.lastName; }
set { this.lastName = value; }
}
}
In this example, I have three instance fields and three properties. The instance fields are internal to my Person class and store the actual values. The three properties expose the values of the instance fields to public clients and allow the values of the fields to be set.
I can accomplish the same thing in Objective-C:
@interface Person {
NSString *firstName;
NSString *middleName;
NSString *lastName;
}
- (NSString *)firstName;
- (void)setFirstName:(NSString *)value;
- (NSString *)middleName;
- (void)setMiddleName:(NSString *)value;
- (NSString *)lastName;
- (void)setLastName:(NSString *)value;
@end
@implementation Person
- (NSString *)firstName {
return firstName;
}
- (void)setFirstName:(NSString *)value {
if (firstName == value) {
return;
}
[firstName release];
firstName = [value copy];
}
- (NSString *)middleName {
return middleName;
}
- (void)setMiddleName:(NSString *)value {
if (middleName == value) {
return;
}
[middleName release];
middleName = [value copy];
}
- (NSString *)lastName {
return lastName;
}
- (void)setLastName:(NSString *)value {
if (lastName == value) {
return;
}
[lastName release];
lastName = [value copy];
}
- (void)dealloc {
[firstName release];
[middleName release];
[lastName release];
[super dealloc];
}
Remember from the discussion on memory management, I have to be responsible for retaining and releasing the object references that my Person object holds. In the setters for the properties, I’m checking to see if the new object that the property is being set to is equal to the current value. If the values are the same, I stop there. If the values are different, I first release the name and set my field value to a copy of the given NSString object.
Why am I copying the NSString objects instead of just assigning the value? I actually messed up on this in my last post. I was doing the assignment by value. The answer is going to take us on a quick little tangent. Let’s add an initializer (constructor) to this Person object like we did in the last post and take a look at the implementation:
- (id)initWithFirstName:(NSString*)first
middleName:(NSString *)middle
lastName:(NSString *)last {
if (self = [super init]) {
self.firstName = first;
self.middleName = middle;
self.lastName = last;
}
}
In this initializer, the very first line of code is “if (self = [init super]).” In .NET and other languages, we have the concept of the this pointer, which points to the object instance. Objective-C has the same concept, but it’s called self. The difference is that while the this pointer is set automatically by .NET or C++ by the new operator, in Objective-C, the self pointer is constructed in the initializer. We’re just assuming at the moment that [init super] is setting self to an instance of the Person class. A client of Person may also be assuming that calling initWithFirstName:middleName:lastName: is going to return an instance of a Person object. That’s not always the case. Remember how a Person object is going to be allocated and initialized in an Objective-C program:
Person *person = [[Person alloc] initWithFirstName:@"Michael"
middleName:@"Francis"
lastName:@"Collins"];
First, we’re calling the alloc class method that returns an id value, which we assume to be a pointer to the uninitialized memory for a Person object. Then we call the initWithFirstName:middleName:lastName: method on the new uninitialized Person object. But remember that our initializer also returns an id value. What’s to prevent our Person initializer, or some other initializer in a superclass from returning something other than a Person object? Nothing. The initializer can return an object of whatever type it wants to as long as it returns an object that conforms to the same interface as Person. So while I think that I am creating an instance of Person, I might be creating an instance of AmericanPerson, BrazilianPerson, FrenchPerson, or StephenColbert, all of which are subclasses on Person.
This is in fact what can happen with NSString. I made a mistake in my earlier post because I was thinking of NSString in the way that the .NET System.String class or the Java String class work. In .NET and Java, strings are immutable objects. That means that they can be passed around and the value of the string will never change. However, in Objective-C, that’s not necessarily the case. While I might think that I’m allocating an instance of the NSString class (which is immutable), I might instead be getting an instance of NSMutableString which can be changed. So if I set my first name field to a NSMutableString instance, and then the string is altered by somebody else, my first name is going to be different without my knowledge.
In this case, what I really want is to copy the string value so that my first name is set by value and not reference. If my first name is set to an instance of NSMutableString, by making a copy of it, my first name will not change if the source string is altered.
Now back to the main point of this post, there’s a lot of work to go on in our setters. In .NET 3.0, we received the wonderful gift of automatic properties that implement the basic getters and setters for us. I can rewrite my .NET Person object to look like this instead:
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
The automatic properties in .NET automatically define my field and the implementation of the getters and setters. In Objective-C, we still have to define our fields, but the property implementations can be automatically generated for us using the @property and @synthesize directives:
@interface Person {
NSString *firstName;
NSString *middleName;
NSString *lastName;
}
@property (copy) NSString *firstName;
@property (copy) NSString *middleName;
@property (copy) NSString *lastName;
@implementation Person
@synthesize firstName;
@synthesize middleName;
@synthesize lastName;
@end
This is a lot less code, isn’t it?
The magical power of properties in Objective-C compared to .NET is that you can tell the Objective-C compiler exactly how you want your properties to be implemented. This isn’t a knock on .NET, because .NET is a garbage collected environment, so the memory management features aren’t really relevant. But in Objective-C, you’ll notice that I had (copy) following my @property directive. The items inside the parentheses are attributes that affect how the compiler will generate the property getters and setters. The following set of attributes are defined for properties:
- readwrite: generates a getter method and a setter method (default behavior)
- readonly: generates only a getter method
- assign: assigns the property by value (default behavior). You’ll want to use this for value types (ints, floats, etc.) and not for object types because of the memory management concerns.
- retain: used for object types. This will cause the previous value to be released and the new value to be retained.
- copy: used for object types. This will cause the previous value to be released and the new value to be copied or cloned.
- nonatomic: by default, calls to the getter or setter will be synchronized, meaning that only one thread may access the object at a time. In a single-threaded environment or case where the object will not be accessed by multiple threads, you can specify nonatomic to skip the generation of the synchronization code.
While the @property directive specifies the property in your class interface, in the implementation the @synthesize directive will actually cause the compiler to generate the code for the getter and setter of the property. By default, the @synthesize directive will attempt to generate a getter and setter to get and set the value of a field with the same name of a property. However, the field may not always be the same name as the property. In this case, the following syntax may be used:
@interface Person {
NSString *givenName;
}
@property (copy) firstName;
@end
@implementation Person
@synthesize firstName = givenName;
@end
In this example, the @synthesize directive is given the name of the field to generate the getter and setter for. This becomes important for Key-Value Coding and BOOL values where the property name has to start with the is prefix, but that’s a topic for another post.
In this post, I’ve discussed the use and definition of automatic properties in Objective-C. I took a tangent to explain how objects are initialized and different objects can be returned by initializers. I also explained the benefits of copying objects in certain cases versus retaining references to objects such as strings when the strings are mutable. Finally, I showed how to use attributes to control how the getters and setters for properties are generated by the Objective-C compiler.