Advertisement

Objective-C Succinctly: Properties

by

Now that we've explored what data types are available, we can talk about actually using them in a productive manner. We learned how to declare properties in Hello, Objective-C, but this chapter dives deeper into the nuances behind public properties and instance variables. First, we'll take a quick look at the basic syntax of properties and instance variables, and then we'll discuss how to use behavior attributes to modify accessor methods.


Declaring Properties

Properties can be declared in an interface using the @property directive. As a quick review, let's take a look at the Person.h file we created in the Hello, Objective-C chapter:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (copy) NSString *name;

@end

This declares a property called name of type NSString. The (copy) attribute tells the runtime what to do when someone tries to set the value of name. In this case, it creates an independent copy of the value instead of pointing to the existing object. We'll talk more about this in the next chapter, Memory Management.


Implementing Properties

In Hello, Objective-C, we used the @synthesize directive to automatically create getter and setter methods. Remember that the getter method is simply the name of the property, and the default setter method is setName:

#import "Person.h"

@implementation Person

@synthesize name = _name;

@end

But, it's also possible to manually create the accessor methods. Doing this manually helps to understand what @property and @synthesize are doing behind the scenes.

Included code sample: ManualProperty

First, add a new property to the Person interface:

@property (copy) NSString *name;
@property unsigned int age;

Note that we're storing age as a primitive data type (not a pointer to an object), so it doesn't need an asterisk before the property name. Back in Person.m, define the accessor methods explicitly:

- (unsigned int)age {
	return _age;
}

- (void)setAge:(unsigned int)age {
	_age = age;
}

This is exactly what @synthesize would have done for us, but now we have the chance to validate values before they are assigned. We are, however, missing one thing: the _age instance variable. @synthesize automatically created a _name ivar, allowing us to forgo this for the name property.


Instance Variables

Instance variables, also known as ivars, are variables intended to be used inside of the class. They can be declared inside of curly braces after either the @interface or @implementation directives. For example, in Person.h, change the interface declaration to the following:

@interface Person {
	unsigned int _age;
}

This defines an instance variable called _age, so this class should now compile successfully. By default, instance variables declared in an interface are protected. The equivalent C# class definition would be something like:

class Person {
	protected uint _age;
}

Objective-C scope modifiers are the same as in C#: private variables are only accessible to the containing class, protected variables are accessible to all subclasses, and public variables are available to other objects. You can define the scope of instance variables with the @private, @protected, and @public directives inside of @interface, as demonstrated in the following code:

@interface Person : NSObject {
  @private
	NSString *_ssn;
  @protected
	unsigned int _age;
  @public
	NSString *job;
}

Public ivars are actually a bit outside Objective-C norms. A class with public variables acts more like a C struct than a class; instead of the usual messaging syntax, you need to use the -> pointer operator. For example:

Person *frank = [[Person alloc] init];
frank->job = @"Astronaut";
NSLog(@"%@", frank->job);
// NOT: [frank job];

However, in most cases, you'll want to hide implementation details by using an @property declaration instead of public instance variables. Furthermore, because instance variables are technically implementation details, many programmers like to keep all instance variables private. With this in mind, ivars declared in @implementation are private by default. So, if you were to move the _age declaration to Person.m instead of the header:

@implementation Person {
	unsigned int _age;
}

_age would be scoped as a private variable. Keep this in mind when working with instance variables in subclasses, as the different defaults for interface versus implementation declaration can be confusing for newcomers to Objective-C.

Customizing Accessors

But enough about instance variables; let's get back to properties. Accessor methods can be customized using several property declaration attributes (e.g., (copy)). Some of the most important attributes are:

  • getter=getterName - Customize the name of the getter accessor method. Remember that the default is simply the name of the property.
  • setter=setterName - Customize the name of the setter accessor method. Remember that the default is set followed by the name of the property (e.g., setName ).
  • readonly - Make the property read-only, meaning only a getter will be synthesized. By default, properties are read-write. This cannot be used with the setter attribute.
  • nonatomic - Indicate that the accessor methods do not need to be thread safe. Properties are atomic by default, which means that Objective-C will use a lock/retain (described in the next chapter) to return the complete value from a getter/setter. Note, however, that this does not guarantee data integrity across threads-merely that getters and setters will be atomic. If you're not in a threaded environment, non-atomic properties are much faster.

A common use case for customizing getter names is for Boolean naming conventions. Many programmers like to prepend is to Boolean variable names. This is easy to implement via the getter attribute:

@property (getter=isEmployed) BOOL employed;

Internally, the class can use the employed variable, but other objects can use the isEmployed and setEmployed accessors to interact with the object:

Person *frank = [[Person alloc] init];
[frank setName:@"Frank"];
[frank setEmployed:YES];
if ([frank isEmployed]) {
	NSLog(@"Frank is employed");
} else {
	NSLog(@"Frank is unemployed");
}

Many of the other property attributes relate to memory management, which will be discussed in the upcoming section. It's also possible to apply multiple attributes to a single property by separating them with commas:

@property (getter=isEmployed, readonly) BOOL employed;

Dot Syntax

In addition to getter/setter methods, it's also possible to use dot notation to access declared properties. For C# developers, this should be much more familiar than Objective-C's square-bracket messaging syntax:

Person *frank = [[Person alloc] init];
frank.name = @"Frank";    // Same as [frank setName:@"Frank"];
NSLog(@"%@", frank.name); // Same as [frank name];

Note this is just a convenience-it translates directly to the getter/setter methods described previously. Dot notation cannot be used for instance methods.


Summary

Properties are an integral aspect of any object-oriented programming language. They are the data that methods operate on. The @property directive is a convenient way to configure a property's behavior, but it doesn't do anything that can't be done by manually creating getter and setter methods.

In the next chapter, we'll take a detailed look at how properties are stored in memory, as well as a few new property attributes for controlling this behavior. After that, we'll dive into methods, which rounds out the core object-oriented tools of Objective-C.

This lesson represents a chapter from Objective-C Succinctly, a free eBook from the team at Syncfusion.
Advertisement